Background
I have an Angular component that renders an Angular Material Autocomplete field. The field has three options, and each option contains a name
and an id
. When an option is selected, it's name
field should be displayed in the field.
Problem
When an option is selected initially, the name
field is displayed as expected. However, when the same option is selected again, while it is already selected, the id
of the option is displayed in the field. The name
of the selected option should be displayed, and not the id
.
Question
How can this be fixed so that, regardless of how many times an option is selected, the name
field is always displayed, and not the id
field?
TS
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
MatAutocompleteModule,
MatAutocompleteSelectedEvent,
} from '@angular/material/autocomplete';
import { MatInputModule } from '@angular/material/input';
interface Item {
id: string;
name: string;
}
@Component({
selector: 'app-autocomplete',
standalone: true,
templateUrl: './autocomplete.component.html',
styleUrls: ['./autocomplete.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, FormsModule, MatAutocompleteModule, MatInputModule],
})
export class AutocompleteComponent {
options: Item[] = [
{ id: '1', name: 'Item One' },
{ id: '2', name: 'Item Two' },
{ id: '3', name: 'Item Three' },
];
name: string = '';
id: string = '';
placeholder: string = 'Select an option';
resetField() {
this.name = '';
}
selectionChange(e: MatAutocompleteSelectedEvent) {
this.name = e.option.viewValue;
console.log(e.option.viewValue); // name
console.log(e.option.value); // id
}
}
HTML
<mat-form-field>
<mat-label>{{ placeholder }}</mat-label>
<input
#assetInput
type="text"
[placeholder]="placeholder"
matInput
[(ngModel)]="name"
[matAutocomplete]="auto"
/>
<mat-autocomplete
#auto="matAutocomplete"
(optionSelected)="selectionChange($event)"
>
<mat-option *ngFor="let option of options" [value]="option.id">
{{ option.name }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
We can use displayWith
property binding, where we supply a function that displays the correct value.
We require .bind(this)
so that the function can access the component scope, even if it executed inside the mat autocomplete component.
displayFn(id: string) {
if (!id) return '';
let index = this.options.findIndex((item) => item.id === id);
return this.options?.[index]?.name || id;
}
@Input()
displayWith: ((value: any) => string) | null
Function that maps an option's control value to its display value in the trigger.
<mat-form-field>
<mat-label>{{ placeholder }}</mat-label>
<input
#assetInput
type="text"
[placeholder]="placeholder"
matInput
[(ngModel)]="name"
[matAutocomplete]="auto"
/>
<mat-autocomplete
#auto="matAutocomplete"
[displayWith]="displayFn.bind(this)"
(optionSelected)="selectionChange($event)"
>
<mat-option *ngFor="let option of options" [value]="option.id">
{{ option.name }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
MatAutocompleteModule,
MatAutocompleteSelectedEvent,
} from '@angular/material/autocomplete';
import { MatInputModule } from '@angular/material/input';
interface Item {
id: string;
name: string;
}
@Component({
selector: 'app-autocomplete',
standalone: true,
templateUrl: './autocomplete.component.html',
styleUrls: ['./autocomplete.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, FormsModule, MatAutocompleteModule, MatInputModule],
})
export class AutocompleteComponent {
options: Item[] = [
{ id: '1', name: 'Item One' },
{ id: '2', name: 'Item Two' },
{ id: '3', name: 'Item Three' },
];
name: string = '';
id: string = '';
placeholder: string = 'Select an option';
resetField() {
this.name = '';
}
selectionChange(e: MatAutocompleteSelectedEvent) {
this.name = e.option.viewValue;
console.log(e.option.viewValue); // name
console.log(e.option.value); // id
}
displayFn(id: string) {
if (!id) return '';
let index = this.options.findIndex((item) => item.id === id);
return this.options?.[index]?.name || id;
}
}