Search code examples
angulartypescriptngfor

Angular ngFor - TS2339: Property 'value' does not exist on type 'OptionItem'


I think I may be slowly going mad today but hear me out. I am making an Angular component that takes an Input() of either a string array or custom object array and then applies this to an HTML select menu options using ngFor with some logic to determine if the Input() is a string array or not.

Here is my component, notice how I have introduced types at the top of my component:

import {
  Component,
  Input,
  OnInit
} from '@angular/core';

type OptionValue = Readonly<{
  title: string;
  value: string;
}>;

type OptionItem = OptionValue | string;

@Component({
  selector: 'app-select',
  templateUrl: './select.component.html',
  styleUrls: ['./select.component.css'],
  template_: `
    <select *ngIf="options?.length">
      <!-- for simple string arrays -->
      <ng-container *ngIf="isStingSelect">
        <option *ngFor="let option of options" [attr.value]="option">
          {{ option }}
        </option>
      </ng-container>
      <!-- for obj arrays -->
      <ng-container *ngIf="!isStingSelect">
        <option *ngFor="let option of options" [attr.value]="option.value">
           {{ option.title }}
        </option>
      </ng-container>
    </select>`
})
export class SelectComponent implements OnInit {
  @Input() options!: OptionItem[];

  isStingSelect = false;

  ngOnInit(): void {
    if (typeof this.options[0] === 'string') {
      this.isStingSelect = true;
    }
  }
}

Now in another component, I add to use my custom select component and pass through the following data:

This is the object array I create in the component template file:

dataOptions = [
  {
    value: 'www.some-url.com',
    title: 'Simple'
  }, 
  {
    value: 'www.some-other-url.com',
    title: 'Live Monitor'
  }
];

and here I am using my custom select component in the template with the data:

<app-select [options]="dataOptions"></app-select>

This produces the following error:

TS2551: Property 'value' does not exist on type 'OptionItem'. Did you mean 'valueOf'?
  Property 'value' does not exist on type 'string'.

10     <option *ngFor="let option of options" [attr.value]="option.value">

For some reason, my OptionValue, OptionItem and OptionItem[] are working correctly? I have been looking at this for some time and can't see what I am doing wrong. Any ideas or help would be appreciated.


Solution

  • Think that this is the default template type checking performed by Angular and complain that OptionItem could be a string type that doesn't have a value property.

    Even if you use a variable to check whether it is a string type or not, this is run-time but not compilation time. Hence, this will not overcome the type-checking issue.

    Think that what you can achieve is combining both <ng-container> handle for the different type into one. And implement the methods to check the option type and return the respective value based on type.

    Rendering items in the *ngFor directive by calling method each could impact the performance if there is a huge set of elements. A recommendation would be to implement the trackBy in the *ngFor directive so that DOM elements will not get destroyed each time and re-render when there is an element added/removed.

    <select *ngIf="options?.length">
      <ng-container>
        <option *ngFor="let option of options; trackBy: trackByFn" [attr.value]="getOptionValue(option)">
          {{ getOptionLabel(option) }}
        </option>
      </ng-container>
    </select>
    
    getOptionLabel(option: OptionItem) {
      return typeof option === 'string' ? option : option.title;
    }
    
    getOptionValue(option: OptionItem) {
      return typeof option === 'string' ? option : option.value;
    }
    
    trackByFn = (index: number, option: OptionItem) => {
      this.getOptionValue(option);
    };
    

    Demo @ StackBlitz