Maxim Palenov
Maxim Palenov

Reputation: 720

Is it possible to have an intersection type with different value types?

I'm trying to compose initialization object type which I can use to initialize mock objects. I don't want to define all fields because I'd like to reuse this type. I defined the custom type like this:

type InitObjType = { [key: string]: string } & { customKey?: Observable<boolean> };

function initializator(obj: InitObjType) {...}

if I try to pass the init object into initializator function like that:

const subject = new Subject<boolean>;

initializator({
  a: 'a',
  b: 'b',
  c: 'c',
  customKey: subject,
});

I receive the error:

error TS2345: Argument of type '{ a: string; b: string; c: string; customKey: Observable<boolean>; }' is not assignable to parameter of type 'InitObjType'.
  Type '{ a: string; b: string; c: string; customKey: Observable<boolean>; }' is not assignable to type '{ [key: string]: string; }'.
    Property 'customKey' is incompatible with index signature.
      Type 'Observable<boolean>' is not assignable to type 'string'.

I use TypeScript 3.5.3.

Are there any ideas why intersection of types doesn't work?

Thanks in advance!

Upvotes: 0

Views: 338

Answers (1)

pascalpuetz
pascalpuetz

Reputation: 5418

There is a good explanation on index types here.

Declaring an index signature basically means that all explicit properties need to conform to the index signature as well. Using an intersection type to workaround this does work when declaring the type - but you will not be able to create the object. There are some discussions about this on github as well.

Using a Subtype

It is considered a best practice to move the index signatures to a subtype like this:

type InitObjType = { 
  customKey?: Observable<boolean>,
  additionalProperties: { [key: string]: string }
};

function initializator(obj: InitObjType) {...}

const subject = new Subject<boolean>;

initializator({
  customKey: subject,
  additionalProperties: {
    a: 'a',
    b: 'b',
    c: 'c',
  }
});

Using less type-safe options

If you absolutely have to keep the additonal properties on the same level, you have to use a less type safe way. Either by changing the index signature:

type InitObjType = { 
  customKey?: Observable<boolean>,
  [key: string]: string | Observable<boolean>
};

// Or even less type-safe with any

type InitObjType = { 
  customKey?: Observable<boolean>,
  [key: string]: any
};

Or by typecasting when creating the object:

const subject = new Subject<boolean>;

initializator({
  a: 'a',
  b: 'b',
  c: 'c',
  customKey: subject,
} as any);

This last example has an interesting sideeffect. Since TypeScript only hinders you from creating an object of this type (thus you have to cast it to any), you can still cast that object back to get type safety again (the tradeoff being that you have to type it out each time).

const subject = new Subject<boolean>;

initializator({
  a: 'a',
  b: 'b',
  c: 'c',
  customKey: subject,
} as any as InitObjType);

Upvotes: 2

Related Questions