Home > Back-end >  Typescript get property only available in one of union
Typescript get property only available in one of union

Time:07-19

To start with: I'm a total Typescript noob so please bear with me.

I've made a small use-case(At the bottom of this post) in which I have an array which can contain both "Class One" and "Class Two". I want to loop through that array with a forEach and act when the property drawer is present.

The problem that Typescript gives me in my forEach loop(Where I try to log drawer) is:

Property 'drawer' does not exist on type 'One | Two'.
  Property 'drawer' does not exist on type 'Two'.

Whilst it is clearly present in One.

When I set my arrayWithBothOneAndTwo to let arrayWithBothOneAndTwo = [] as One[] it won't give me the problem of drawer not existing in my forEach loop but since it does contain Two I don't think thats a proper solution and will throw other errors.

Changing my forEach loop to

    arrayWithBothOneAndTwo.forEach((item: One) => {
      console.log(item.drawer)
    })

Gives me the error:

Argument of type '(item: One) => void' is not assignable to parameter of type '(value: One | Two, index: number, array: (One | Two)[]) => void'.
  Types of parameters 'item' and 'value' are incompatible.
    Type 'One | Two' is not assignable to type 'One'.
      Property 'drawer' is missing in type 'Two' but required in type 'One'.

So in Short:

How can I use item.drawer in my forEach without throwing errors in my code?

    class One {
      drawer: string[] = []

      constructor () {
        this.drawer.push('A')
        this.drawer.push('B')
        this.drawer.push('C')
      }
    }

    class Two {
      notADrawer: string[] = []

      constructor () {
        this.notADrawer.push('D')
        this.notADrawer.push('E')
        this.notADrawer.push('F')
      }
    }

    // make an array with some one classes
    const someOnes = [new One(), new One()]
    // make an array with some two classes
    const someTwos = [new Two(), new Two(), new Two(), new Two()]

    // init an array which can contain both One or Two 
    let arrayWithBothOneAndTwo = [] as Array<One | Two>

    // populate array with some One classes and some Two classes
    arrayWithBothOneAndTwo = [...someOnes, ...someTwos]
    
    // Loop through it and show the drawer array (if present)
    arrayWithBothOneAndTwo.forEach((item) => {
      console.log(item.drawer)
    })

CodePudding user response:

To get TypeScript to correctly narrow the type, you have to add a check to see whether the property is present:

arrayWithBothOneAndTwo.forEach((item) => {
    if('drawer' in item) {
        console.log(item.drawer); // type of item now narrowed to One
    }
});

If you want, you can make that more explicit with a type predicate in a user-defined type guard:

arrayWithBothOneAndTwo.forEach((item) => {
    if(isOne(item)) {
        console.log(item.drawer);
    }
});

function isOne(item: One | Two): item is One {
  return 'drawer' in item;
}
  • Related