TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Combining destructuring with parameter properties

Open buzinas opened this issue 10 years ago • 103 comments

Today, we can take advantage of parameter properties to reduce the boilerplate, e.g:

class Person {
  constructor(public firstName: string, public lastName: number, public age: number) {

  }
}

Since 1.5, we can also use destructuring, e.g:

class Person {
  firstName: string;
  lastName: string;
  age: number;

  constructor({ firstName, lastName, age } : { firstName: string, lastName: string, age: number }) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
}

I've tried in many ways to combine both features, but had no success. So:

  1. Is it possible to combine them nowadays, and if yes, how?
  2. If not, could it be an improvement to a future TypeScript version? E.g:
class Person {
  constructor(public { firstName, lastName, age } : { firstName: string, lastName: string, age: number }) {

  }
}

// the code above would possibly transpile to:
var Person = (function () {
    function Person(_a) {
        var firstName = _a.firstName, lastName = _a.lastName, age = _a.age;
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    return Person;
})();

buzinas avatar Oct 19 '15 15:10 buzinas

I like this.

xLama avatar Oct 20 '15 15:10 xLama

@buzinas I originally had this working in #1671, but didn't take it in (check out #1541). I think the issue was that it was relatively dense in semantics. I don't necessarily agree, but maybe someone else on the team can weigh in.

DanielRosenwasser avatar Oct 20 '15 19:10 DanielRosenwasser

Note that in those proposals the parameter property modifier applied to the entire binding pattern (simpler) as opposed to the original suggestion here which in theory supports different visibility modifiers per destructured element (not sure that level of specificity would be worth it).

danquirk avatar Oct 20 '15 19:10 danquirk

@danquirk For me, if it's easier for you to do the other way, I don't really care. In fact, that was my first try (e.g public {firstName, lastName, age}), and as soon as it didn't work, I tried to use on each property, and it didn't work too.

It would be great if we could support both (since not always we want to create properties for all the parameters, and when we want, it would be simpler to use public/private only once), but if it's easier to support only one approach, it will be great already.

Probably it's something that people will use more and more, since destructuring is awesome.

buzinas avatar Oct 20 '15 19:10 buzinas

just ran into this. I think either approach would satisfy most use cases. Hope to see this in a future version.

robianmcd avatar Nov 26 '15 22:11 robianmcd

The first thing I tried was:

  constructor(thisProps: { public myProperty: string, public myOtherProperty: number }) {
    // etc
  }

Something like this would be a nice-to-have.

laurelnaiad avatar Dec 23 '15 21:12 laurelnaiad

Definitely +1 this. The alternative tends to be... bulky.

jack-guy avatar Feb 26 '16 05:02 jack-guy

:+1:

albertywu avatar Mar 23 '16 18:03 albertywu

:+1:

hrundik avatar Apr 19 '16 18:04 hrundik

+1

aliatsis avatar Apr 27 '16 07:04 aliatsis

+1

lekev avatar May 13 '16 10:05 lekev

+1

andriilazebnyi avatar Jun 13 '16 14:06 andriilazebnyi

👍

yjaaidi avatar Aug 09 '16 07:08 yjaaidi

+1

TiuSh avatar Oct 11 '16 15:10 TiuSh

Please use the GitHub reactions feature rather than standalone upvote comments. Thanks!

RyanCavanaugh avatar Oct 11 '16 21:10 RyanCavanaugh

In an attempt to be more DRY using named args and strong types (until something like your proposal lands), I tried this:

interface ExampleArgs {
  firstArg: string;
  otherArg: number;
}

export default class Example implements ExampleArgs {
  firstArg;
  otherArg;

  constructor(kwargs:ExampleArgs) {
    return Object.assign(this, kwargs);
  }
}

but got Member 'firstArg' implicitly has an 'any' type. errors for every argument. ☹️

appsforartists avatar Oct 27 '16 22:10 appsforartists

Write this instead

interface ExampleArgs {
  firstArg: string;
  otherArg: number;
}

export default class Example {
  constructor(kwargs:ExampleArgs) {
    return Object.assign(this, kwargs);
  }
}
export interface Example extends ExampleArgs { }

RyanCavanaugh avatar Oct 27 '16 22:10 RyanCavanaugh

Thanks. I had to separate the exports from the declarations to make that work:


interface ExampleArgs {
  firstArg: string;
  otherArg: number;
}

class Example {
  constructor(kwargs:ExampleArgs) {
    return Object.assign(this, kwargs);
  }
}

export default Example;
interface Example extends ExampleArgs { }

appsforartists avatar Oct 27 '16 22:10 appsforartists

This would be incredibly useful for hydrating class-based models from JSON.

As in

export interface PersonDto {
    name?: string;
}

export class Person {
    constructor(public {name}: PersonDto = {}) {
    }
}

ThatRendle avatar Nov 11 '16 16:11 ThatRendle

Meanwhile we get this feature, here's the workaround:

export class PersonSchema {
    firstName: string;
    lastName: string;
    email?: string; // Thanks to TypeScript 2, properties can be optional ;)
}

export class Person extends PersonSchema {

    constructor(args: PersonSchema = {}) {
        Object.assign(this, args);
    }

}

The side effect is that if args has extra fields they will be copied into your new object.

This will also be your copy constructor.

yjaaidi avatar Nov 11 '16 17:11 yjaaidi

Accepting PRs to implement constructor(public {name1, name2}) forms (i.e. no public / private etc inside the {}s).

RyanCavanaugh avatar Nov 15 '16 00:11 RyanCavanaugh

@RyanCavanaugh, you're proposing that

constructor(public { name1 = value1, name2 = value2 }: { name1: string, name2: string }) {}

would desugar to

constructor({ name1 = value1, name2 = value2 }) {
  this.name1 = name1;
  this.name2 = name2;
}

right?

appsforartists avatar Nov 15 '16 00:11 appsforartists

Why is this discussion only about constructors? Wouldn't this apply to other methods as well?!?

dmoebius avatar Dec 02 '16 07:12 dmoebius

It can't apply to other methods as only the constructor can declare properties. On Fri, 2 Dec 2016 at 08:54, Dirk Möbius [email protected] wrote:

Why is this discussion only about constructors? Wouldn't this apply to other methods as well?!?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Microsoft/TypeScript/issues/5326#issuecomment-264394882, or mute the thread https://github.com/notifications/unsubscribe-auth/ACjP4sBomOO9wSXlIcw9NiDGf9-gZ2smks5rD86jgaJpZM4GRcCk .

yjaaidi avatar Dec 02 '16 17:12 yjaaidi

+1

alexjlockwood avatar Dec 26 '16 00:12 alexjlockwood

So, will this ever be implemented?

AppLover69 avatar Mar 16 '17 15:03 AppLover69

class Person {
    
    firstName: string
    lastName: string
    age: number
    
    constructor(_: Person) {
        Object.assign(this, _)
    }
}

class PersonWithEmail extends Person {
    
    email: string
    
    constructor(_: PersonWithEmail) {
        super(_)
    }
}

let p = new PersonWithEmail({
    firstName: '',
    lastName: '',
    age: 0,
    email: '',
})

andraaspar avatar Jun 22 '17 13:06 andraaspar

@andraaspar I'd imagine you'd run into issues if there were methods on Person.

const model = {
  firstName: 'Andras',
}

class Person {
  firstName: string;
  doAThing() {
  }
}

const thisShouldError: Person = model;

appsforartists avatar Jun 22 '17 14:06 appsforartists

@appsforartists Sure you would. Not a replacement for the real thing. But it's elegant for model objects, until it gets implemented.

andraaspar avatar Jun 22 '17 15:06 andraaspar

Hi, personally I'm using a Schema interface to avoid issues with methods etc... there's an example here: https://blog.wishtack.com/2017/05/06/angular-2-components-communication-using-reactive-stores/

The problem with Object.assign is that you might end up with dynamically added properties. Example:

let data = {firstName: 'Foo', superfluousProperty: 'something'};
let person = new Person(data);
console.log(person['superfluousProperty']) // 'something'

It would be nice to be able to use keyof to generate a runtime array of class properties but this is not available for the moment. There are also some experimental approaches like this one https://github.com/kimamula/ts-transformer-keys. Anyway, I'm sure, we'll get something nice soon :)

yjaaidi avatar Jun 24 '17 10:06 yjaaidi