Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Typescript get property only available in one of union

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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)
    })

>Solution :

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;
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading