2

When writing plain Javascript, i often build classes with a "config" property, whose keys have some default values, for example:

class MyClass{
    config = {
        someProp:true,
        otherProp:5
    }

    constructor(config){
        for(let prop in config){
            this.config[prop] = config[prop] 
        }
    }
}

This is very convenient, because i can just loop through the passed object and override defaults(if a value is provided)

In Typescript, i tried something like this:

class MyClass{
    config = {
        someProp:true,
        otherProp:5
    }  

    constructor(config?: { someProp?: boolean,otherProp?:number  }) {
        if (config) {
            for (let prop in config) {
                this.config[prop] = config[prop]
            }
        }

    }
}

I get the following error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ someProp: boolean; otherProp: number; }'. No index signature with a parameter of type 'string' was found on type '{ someProp: boolean; otherProp: number; }'.

I understand more or less the meaning of this error, but i guess my whole approach here is very "JS-like", and should be changed.

What would be the conventional, simple way to do this in Typescript? The idea is to quickly merge the provided properties, with the default ones, without repeating various checks.

i.brod
  • 3,993
  • 11
  • 38
  • 74
  • Does this answer your question? [TypeScript - Element implicitly has an 'any' type because expression of type 'string' can't be used to index type](https://stackoverflow.com/questions/57438198/typescript-element-implicitly-has-an-any-type-because-expression-of-type-st) – dwjohnston Oct 12 '20 at 12:03
  • The problem is type widening in your `for (let prop in config)` -> TypeScript is infering the value of prop as 'string' and not as `"someProp" | "otherProp"` – dwjohnston Oct 12 '20 at 12:05
  • dwjohnston..I don't understand. Can you explain? – i.brod Oct 12 '20 at 12:07
  • Read the answer in the linked post. FWIW a spread as per the answer here is cleanest solution imo. But it's important to understand type inference and type widening/narrowing in TypeScript, you're going to run into it again. – dwjohnston Oct 12 '20 at 12:20

2 Answers2

4
this.config = {...this.config, ...config }

seems like it might be what you're after.

You might also choose to declare the type of config elsewhere (a type/interface declaration) then use Partial<T> as your constructor parameter.

So:

interface Config { someProp: boolean; otherProp:number;  }

class MyClass{
    config:Config = {
        someProp:true,
        otherProp:5
    }  

    constructor(config?: Partial<Config>) {
        this.config = {...this.config, ...config }    
    }
}
spender
  • 117,338
  • 33
  • 229
  • 351
3

You can sort of do an end-run around the error like this:

constructor(config?: { someProp?: boolean,otherProp?:number  }) {
    Object.assign(this.config, config);
}

Playground link

That sort of hides the JavaScript-y part behind the Object.assign call. :-)

But if you want something with better type checking —this is TypeScript, right? :-) — I'd probably replace the config property and use spread syntax:

class MyClass {
    config = {
        someProp:true,
        otherProp:5
    }  

    constructor(config?: { someProp?: boolean,otherProp?:number  }) {
        if (config) {
            this.config = {...this.config, ...config};
        }
    }
}

Playground link

or perhaps even

class MyClass {
    config: {someProp: boolean, otherProp: number};
    constructor(config?: { someProp?: boolean,otherProp?:number  }) {
        this.config = {
            someProp: true,
            otherProp: 5,
            ...config
        };
    }
}

Playground link

Spread syntax will do nothing if config is undefined (or null).

You could even use a defined type to avoid writing the same props in two places:

interface MyClassConfig {
    someProp: boolean;
    otherProp: number;
}
class MyClass {
    config: MyClassConfig;
    constructor(config?: Partial<MyClassConfig>) {
        this.config = {
            someProp: true,
            otherProp: 5,
            ...config
        };
    }
}

Playground link

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875