10

Need to assign porperties to the only defined from the interface if used with spread operator.

I am expecting a solution for using typescript constructor with spread operator.

For eg.

export interface IBrand {
    id: string;
    name: string;
}

export class Brand implements IBrand {
    id: string;
    name: string;       

    constructor(data: IBrand) {
        this.id = data.id;
        this.name = data.name;
    }
}

this is one option so the moment when I call this class like this, no way how many members are there but I would be having only id, name to final object.

new Brand({...data })

Case: But when I have 25+ keys to the data

export interface IBrand {
    id: string;
    name: string;
}

export class Brand implements IBrand {
    id: string;
    name: string;       

    constructor(data: Partial<IBrand>) {
        Object.assign(this, data);
    }
}

I am expecting not to write all properties again constructor. No matter it has 25 it would be just assigning that existing properties would be assigned. If data has section, genre or any other property it would be ignored.

My 2nd snippet doesn't work.

3
  • But there are no existing properties before the constructor. Commented May 8, 2019 at 12:10
  • You need to mention "id" and "name" as runtime values if you want something to happen at runtime. The easiest way to do this is to mention those keys in an array. (e.g. in TS3.4, (["id", "name"] as const).forEach(k => this[k] = data[k]);). The only way to avoid repeating the words "id" and "name" in the interfaces/classes and at runtime is to create a runtime constant array with those names in them and then derive the interface/class definitions from them (instead of the other way around which won't work) but that is a lot of type juggling just to avoid writing a few string literals. Commented May 8, 2019 at 14:02
  • Try this stackoverflow.com/a/58788876/2746447 Commented Nov 10, 2019 at 13:09

3 Answers 3

9

This doesn't use the spread operator, but you could use the method from this answer

constructor(data: IBrand) {
    for (let key in data) {
        this[key] = data[key];
    }
}

then instantiate...

new Brand(data);
Sign up to request clarification or add additional context in comments.

1 Comment

this sounds dangerous
6

I find a better solution for recurring code using class-transformer package. It also helps to expose and transform input into the required output.

Comments

-1

If you are willing to forego inheritance and encapsulation and embrace composition instead, then you can use types instead of classes and interfaces. IMHO this is where TypeScript really shines.

I am going to use a slightly different scenario than your Brand example, because I think it demonstrates the power of TypeScript better.

Lets start by defining two types:

type Person = {
    id: string
    name: string
}

type Editable = {
    canEdit: boolean
    save(): Promise<boolean>
}

I can compose these into a new type:

type EditablePerson = Editable & Person

This allows me to construct a new person like this:

const person: Person = { id: '12345', name: 'Martin' }

then use this person to construct an EditablePerson like this

const editablePerson: EditablePerson = {
    ...person,
    canEdit: true,
    save: () => {
        console.log(this.name)
        return Promise.resolve(true)
    }
}

I can also write an EditablePerson constructor function that uses the spread operator like this:

const EditablePerson = function(editable: Editable, person: Person): EditablePerson {
    return { ...editable, ...person }
}

Which allows me to construct an EditablePerson like this:

editablePerson = EditablePerson(
    { canEdit: true, save:() => Promise.resolve(true) }, 
    person)

Using the structured typing system in TypeScript brings you much closer to JavaScript and is less like writing in other OOP languages. I really love this style of writing TypeScript because I have long been an opponent of inheritance and always preferred composition.

Using this approach, your Brand example might look something like:

type IBrand {
    id: string
    name: string
}

type Brand = {
    brandField?: string
    brandMethod: () => string
} & IBrand

const Brand = function(brand: IBrand): Brand {
    return { ...brand, brandMethod: () => 'Hello from Brand' }
}

const myBrand: IBrand = Brand({ id: 'abc', name: 'Wonderful Products!' })

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.