user776686
user776686

Reputation: 8655

how do I properly write function signature to accept union-typed parameter?

I am trying to provide an API with a function that accepts a parameter that can be of type A or B. I am using interfaces to type the parameters. Those type do not share a single member. My attempts lead to compiler complaining about property x not present on type x.

export interface MyTypeA {
  prop1: string;
  prop2: boolean;
} 
export interface MyTypeB {
  prop3: number;
  prop4: string;
}

doSomething(param1: string, param2: MyTypeA | MyTypeB){
  switch(param1){
    case 'a':
    case 'b': {
      const cf = this.resolver.resolveComponentFactory(MyClassAComponent);
      const component = this.wrap.createComponent(cf);
      component.instance.prop1 = param2.prop1;
      component.instance.prop2 = param2.prop2;
      break;
    }
    case 'c': {
      const cf = this.resolver.resolveComponentFactory(MyClassBComponent);
      const component = this.wrap.createComponent(cf);
      component.instance.prop3 = param2.prop3;
      break;
    }

  }
}

I am not sure this can be done with interfaces, I assume I might rather have to use type, but don't know how.

Upvotes: 1

Views: 50

Answers (1)

Danziger
Danziger

Reputation: 21161

I see 3 possible solutions:

I would go for Tagged Unions if it makes sense for you to combine param1 and param2. Otherwise use User Defined Type Guards.

👉 Cast each single usage of param2:

Too verbose in my opinion if you have to use it many times, but the most straight forward solution:

...

component.instance.prop1 = (param2 as MyTypeA).prop1;
component.instance.prop2 = (param2 as MyTypeA).prop2;

...

This solution will not generate additional code (the cast will be completelly removed from the generated code).

👉 Use Tagged Unions (aka discriminated unions or algebraic data types):

You can combine param1 and param2 and convert your custom types to tagged unions:

export interface MyTypeA {
  param1: 'a' | 'b';
  prop1: string;
  prop2: boolean;
}

export interface MyTypeB {
  param1: 'c';
  prop3: number;
  prop4: string;
}

doSomething(param2: MyTypeA | MyTypeB) {
  switch(param2.param1) {
    case 'a':
    case 'b': {
      // The compiler knows param2 is of type MyTypeA, because its param1
      // property is either 'a' or 'b'.

      const cf = this.resolver.resolveComponentFactory(MyClassAComponent);
      const component = this.wrap.createComponent(cf);

      component.instance.prop1 = param2.prop1;
      component.instance.prop2 = param2.prop2;

      break;
    }

    case 'c': {
      // The compiler knows param2 is of type MyTypeB, because its param1
      // property is 'c'.

      const cf = this.resolver.resolveComponentFactory(MyClassBComponent);
      const component = this.wrap.createComponent(cf);

      component.instance.prop3 = param2.prop3;

      break;
    }
  }
}

This solution will not generate additional code (the interfaces, including the tagged param, will not be present in the generated code).

👉 Use User Defined Type Guards:

You can use User Defined Type Guards to narrow down the type of param2:

export interface MyTypeA {
  prop1: string;
  prop2: boolean;
}

export interface MyTypeB {
  prop3: number;
  prop4: string;
}

function isA(arg: any): arg is MyTypeA {
    return arg.hasOwnProperty('prop1');
}

function isB(arg: any): arg is MyTypeB {
    return arg.hasOwnProperty('prop3');
}

doSomething(param1: string, param2: MyTypeA | MyTypeB) {
  switch(param1) {
    case 'a':
    case 'b': {
      if (!isA(param2)) return;

      // The compiler knows param2 is of type MyTypeA:

      const cf = this.resolver.resolveComponentFactory(MyClassAComponent);
      const component = this.wrap.createComponent(cf);

      component.instance.prop1 = param2.prop1;
      component.instance.prop2 = param2.prop2;

      break;
    }

    case 'c': {
      if (!isB(param2)) return;

      // The compiler knows param2 is of type MyTypeB:

      const cf = this.resolver.resolveComponentFactory(MyClassBComponent);
      const component = this.wrap.createComponent(cf);

      component.instance.prop3 = param2.prop3;

      break;
    }
  }
}

Note this solution will generate additional code, as those isA and isB functions, as well as the calls to them, will be included in the generated code.

Upvotes: 4

Related Questions