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