Thaledric
Thaledric

Reputation: 559

Object not assignable to { [key: string]: any }?

I have this chunk of Typescript code :

interface UnknownParams { [key: string]: any };

interface ComponentParams {
    foo: string;
    bar: string;
}

interface IComponent<ParamsType> {
    params: ParamsType;
}

type ComponentConstructor<T> = (new (params: T) => IComponent<T>) & {
    something: boolean;
    doAnything: (constructor: ComponentConstructor<UnknownParams>) => void;
};

const myComponentConstructor: ComponentConstructor<ComponentParams> = class {
    constructor(params: ComponentParams) {
        this.params = params;
    }
    params: ComponentParams;
    static something: boolean;
    static doAnything(constructor: ComponentConstructor<UnknownParams>) {
        constructor.something = true;
    }
}

myComponentConstructor.doAnything(myComponentConstructor);

On the last line I have this error :

const myComponentConstructor: ComponentConstructor<ComponentParams>

Argument of type 'ComponentConstructor<ComponentParams>' is not assignable to parameter of type 'ComponentConstructor<UnknownParams>'.
  Type 'ComponentConstructor<ComponentParams>' is not assignable to type 'new (params: UnknownParams) => IComponent<UnknownParams>'.
    Types of parameters 'params' and 'params' are incompatible.
      Type 'UnknownParams' is missing the following properties from type 'ComponentParams': foo, bar — ts(2345)

I don't understand why my ComponentConstructor<ComponentParams> in not assignable to ComponentConstructor<UnknownParams>, cause any object could be assigned to { [key: string]: any }, insn't it ?

EDIT : After @DaGardner's answer, I found a solution that is to make doAnything a generic function with a type parameter instead of UnknownParams, but in my case, that function is part of a class as you can see in the code above.

Do I have to add the type parameter to the class like ComponentConstructor<ComponentParams, DoAnythingTypeParameter>, or is there another solution that could solve this problem ?

Thanks

Upvotes: 0

Views: 433

Answers (1)

DAG
DAG

Reputation: 6994

The relation is the other way around. You can see it if you try to do something in doAnything:

function doAnything(constructor: ComponentConstructor<UnknownParams>){

  return new constructor({ test: 3 });

};

this compiles just fine - as it should, since the constructor accepts, as you said, any object.

If we pass myComponent to doAnything myComponent cannot take { test: 3 } and therefore cannot be passed to doAnything.

If this would be this way around:

interface UnknownParams { [key: string]: any };

interface ComponentParams {
    foo: string;
    bar: string;
}

type ComponentConstructor<T> = new (params: T) => any;

function doAnything(constrcutor: ComponentConstructor<ComponentParams>){ /* */};

const myComponent: ComponentConstructor<UnknownParams> = class {
    constructor(params: UnknownParams) {/* ... */}
}

doAnything(myComponent);

It compiles, as the constructor of myComponent is less restrictive than the signature of doAnything requires.

Update after an substantial edit to the question:

I'm not entirely sure what you are trying to achieve but, you can fix the produced typescript errors pretty easily by reusing the generic type off ComponentConstructor

type ComponentConstructor<T> = (new (params: T) => IComponent<T>) & {
    something: boolean;
    doAnything: (constructor: ComponentConstructor<T>) => void;
};

// and then adapting the implementation as well, note the replaced UnknownProps with the actual ComponentProps

const myComponentConstructor: ComponentConstructor<ComponentParams> = class {
    constructor(params: ComponentParams) {
        this.params = params;
    }
    params: ComponentParams;
    static something: boolean;
    static doAnything(constructor: ComponentConstructor<ComponentParams>) {
        constructor.something = true;
    }
}

Upvotes: 1

Related Questions