Combining types: unable to utilize the second optional type within a for loop

I am facing an issue while looping through an array due to the union type. I am wondering what I have overlooked in the code below that is causing Visual Studio Code to not recognize the second optional type for that specific array.

class Menu {
  // name of the menu
 name: string;
  // list of the ingredients in this menu
  // or a list of submenu with its ingredients
 list: string[] | Menu[];
 hasSubList?: boolean;
}

...
menu: Menu[];

ngOnInit() {
  this.loadMenu();
}
ngAfterContentInit() {
  this.print();
}
loadMenu(): void {
  this.menu = [
   {
     name: "toast" 
     list: [
      "slide of bread",
     ],
   },
   {
     name: "Crumble eggs on taste",
     hasSubList: true;
     list: [
      {
        name: "Eggs",
        list: [
         {
           "Eggs",
           "pepper",
           "a pinch of salt",
          }
         ],
       },
       {
        name: "Toast",
        list: [
          "a slide of bread"
         ],
        },
       ],
   },
 ];
}

this print(): void {
  for(let i=0; i<this.menu.length;i++){
    let item = this.menu[i];
    console.log(item.name);
    for(let j=0; i<item.list.length; j++){
      let list = item.list[j];
      if(item.hasSubList) {
         // HERE
         // console intellsense says 
         // "property 'list' does not exist on type 'string | Menu'
         // "property 'list' does not exist on type 'string'
        for(let k=0; k< list.list.length; k++}(
          console.log(list.list[k]);
        }
      } else {
       console.log(list);
      }
}

Recapping the message from the intellsense:

"property 'list' does not exist on type 'string | Menu'
"property 'list' does not exist on type 'string'

Why did it fail to check for Menu? Because 'list' exists as type 'Menu'

Answer №1

As pointed out by @jacobm, the compiler is unable to determine the actual type of the union type based solely on hasSubList.

In order for the compiler to understand, you must provide a way to discriminate it. One approach is to check the type of the item itself without relying on the hasSubList property.

For example:

for(let i=0; i<this.menu.length;i++){
    let item = this.menu[i];

    for(let j=0; i<item.list.length; j++){
        let list = item.list[j];

        // Using the typeof operator can help distinguish the union type
        if(typeof item !== 'string') {
            for(let k=0; k< list.list.length; k++}(
                console.log(list.list[k]);
            }
        } else {
            console.log(list);
        }
    }
}

Another alternative is to utilize for..of loops instead of traditional for loops. While this may be a matter of personal preference, using for..of loops can be more concise and improve readability when the loop index is not needed.

for (let item of menu) {
    for (let list of item.list) {
        if(item !== 'string') {
            for(let childItem of list.list) {
                console.log(childItem);
            }
        } else {
            console.log(list);
        }
    }
}

Answer №2

Given that list[j] is of type string | Menu, any code utilizing it must be able to handle both strings and Menus, not just one or the other. It seems like you are assuming that if hasSubList is true, then list will always contain a list of menus. If this is your intention, you can cast item to a menu using list as Menu. However, it is generally recommended to avoid such casts as they limit the ability of the type checker to catch potential errors in your code.

Answer №3

The error message occurs because Typescript is unable to determine the specific type you are using when you assign it as string | Menu[]. Using a type cast is one way to resolve this issue.

for (let j = 0; i < item.list.length; j++) {
    if (item.hasSubList) {
      let list = item.list[j] as Menu; // use 'as'     
      for (let k = 0; k < list.list.length; k++) {
        console.log(list[k]);
      }
    } else {
      let list = item.list[j];  
      console.log(list);
    }
  }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I encountered a problem while integrating antd and moment.js in my React project

I am currently using the antd date-picker in my React project with TypeScript. Encountered an error: Uncaught Type Error: moment is not a function. If anyone has a solution, please assist me. .tsx file:: const dateFormat = 'MM-DD-YYYY'; < ...

Tips for avoiding the influence of the parent div's opacity on child divs within a Primeng Carousel

I'm struggling to find a solution to stop the "opacity" effect of the parent container from affecting the child containers. In my code, I want the opacity not to impact the buttons within the elements. I have tried using "radial-gradient" for multipl ...

Tips for testing a void method using unit testing

I'm aiming to increase my code coverage by writing unit tests. I have a method that simply triggers a notification without needing a response. How can I write a unit test for it? public error(message?: any, ...optionalParams: any[]) { if (this.is ...

Is there a workaround for utilizing a custom hook within the useEffect function?

I have a custom hook named Api that handles fetching data from my API and managing auth tokens. In my Main app, there are various ways the state variable "postId" can be updated. Whenever it changes, I want the Api to fetch new content for that specific p ...

Spotlight a newly generated element produced by the*ngFor directive within Angular 2

In my application, I have a collection of words that are displayed or hidden using *ngFor based on their 'hidden' property. You can view the example on Plunker. The issue arises when the word list becomes extensive, making it challenging to ide ...

The API call is failing when using getInitialProps in Next.js

I have been trying to retrieve data from an API using the getinitialprops method and axios However, it seems like my code is not working as expected. Here is a snippet of the relevant code in the file pages/index.tsx IndexPage.getInitialProps = async (ctx ...

Angular 5 - Implementing Horizontal Scroll with Clickable Arrows

In my Angular application, I have successfully implemented horizontal scrolling of cards. Now, I am looking to enhance the user experience by adding "Left" and "Right" buttons that allow users to scroll in either direction within their specific msgCardDeck ...

Issue: The module '@nx/nx-linux-x64-gnu' is not found and cannot be located

I'm encountering issues when trying to run the build of my Angular project with NX in GitHub Actions CI. The process fails and displays errors like: npm ERR! code 1 npm ERR! path /runner/_work/myapp/node_modules/nx npm ERR! command failed npm ERR! c ...

Importing modules that export other modules in Typescript

I am facing an issue with two modules and two classes. ModuleA, ModuleB, ClassA, and ClassB are defined as follows: export class ClassA { } export class ClassB { } The modules are structured like this: export * from './ClassA'; export module ...

Retrieving the key from an object using an indexed signature in Typescript

I have a TypeScript module where I am importing a specific type and function: type Attributes = { [key: string]: number; }; function Fn<KeysOfAttributes extends string>(opts: { attributes: Attributes }): any { // ... } Unfortunately, I am unab ...

Steps for making a vertical ion-range control

enter image description hereHello there, I'm trying to create a vertical ion-range instead of a horizontal one in capacitor 4. While I successfully created a horizontal range by following the instructions on https://ionicframework.com/docs/api/range, ...

Assigning a custom class to the cdk-overlay-pane within a mat-select component is restricted to Angular Material version 10.2.7

I attempted the code below, but it only works for angular material 11. My requirement is to use only angular material 10. providers: [ { provide: MAT_SELECT_CONFIG, useValue: { overlayPanelClass: 'customClass' } } ] There a ...

What is the process for including an optional ngModelGroup in Angular forms?

I'm encountering an issue with incorporating the optional ngModelGroup in angular forms. Although I am familiar with how to use ngModelGroup in angular forms, I am struggling to figure out a way to make it optional. I have attempted to pass false, nu ...

What could be causing the profile detail to be missing in the home component?

To optimize performance, I am trying to only call the profile API if there is no detail available. This way, the API will only be called when necessary, such as when the user first visits the home component. I am using route resolve to ensure that the data ...

What could be the reason for my npm package installed globally to not be able to utilize ts-node?

Recently, I've been working on developing a CLI tool for my personal use. This tool essentially parses the standard output generated by hcitool, which provides information about nearby bluetooth devices. If you're interested in checking out the ...

The NbDatePicker does not display certain Spanish names

One issue I'm facing is with the Nb-DatePicker component in my project. I tried using {provide: LOCALE_ID, useValue: "es-MX"} in my app.component to display it in Spanish. However, when February, April, May, August, or December are selected, ...

TypeDoc is having trouble locating the Angular 2 modules

Currently, I am attempting to create documentation files using TypeDoc. When executing TypeDoc with the command below: /node_modules/.bin/typedoc --module system --target ES5 --exclude *.spec.ts* --experimentalDecorators --out typedoc app/ --ignoreCompile ...

Angular4's integration with AngularFireAuth for Google authentication is causing users to be redirected back to the source page before they can successfully

Previously, this feature was functioning perfectly. However, the current issue is that the browser is redirecting to a long URL and then quickly returning to the original source URL. This prevents users from logging into their Google accounts. authentica ...

Identifying Classifications Based on Input Parameters

I'm encountering some difficulty in typing a function that calls an external API and returns data based on the parameters sent. Here is what I have so far... import axios from 'axios'; interface IGetContactArgs { properties?: string[]; } ...

What is causing the .responseToString function to be recognized as not a function?

Consider the following scenario with Typescript: interface IResponse { responseToString(): string; } export default IResponse; We have two classes that implement this interface, namely RestResponse and HTMLResponse: import IResponse from "./IRespo ...