I am building an application that uses Angular Material in Angular 16. I have ngrx state management gathering an array of objects to pick from. I want the input to display the name property of this object when one is picked from the autocomplete options. I am currently using a matInput with mat-autocomplete.
import { Component, OnInit } from '@angular/core';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {AsyncPipe} from '@angular/common';
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatInputModule} from '@angular/material/input';
import {MatFormFieldModule} from '@angular/material/form-field';
import { TokenNameWithId } from '../../../shared/models/card.model';
import { Observable, combineLatest, debounceTime, map, startWith } from 'rxjs';
import { Store } from '@ngrx/store';
import { selectAllTokenNames } from '../+state/token-names.selectors';
@Component({
selector: 'tokenator-tokens-dropdown',
standalone: true,
imports: [FormsModule,
MatFormFieldModule,
MatInputModule,
MatAutocompleteModule,
ReactiveFormsModule,
AsyncPipe
],
providers: [
],
templateUrl: './tokens-dropdown.component.html',
styleUrl: './tokens-dropdown.component.scss',
})
export class TokensDropdownComponent implements OnInit {
public tokenNamesControl = new FormControl<string>('', {
nonNullable: true,
});
public filteredTokenNames!: Observable<TokenNameWithId[]>;
private tokenNames$ = this.store.select(selectAllTokenNames);
constructor(private store: Store) {
}
ngOnInit(): void {
this.filteredTokenNames = combineLatest([
this.tokenNamesControl.valueChanges.pipe(startWith('')),
this.tokenNames$,
]).pipe(
debounceTime(250),
map(([value, tokenNamesWithId]) => value ? this._filter(value, tokenNamesWithId) : tokenNamesWithId
));
}
private _filter(value: string, tokenNamesWithId: TokenNameWithId[]): TokenNameWithId[] {
const filterValue = value.toLowerCase();
return tokenNamesWithId.filter((tokenNameWithId: TokenNameWithId) => tokenNameWithId.displayName.includes(filterValue));
}
}
.example-form {
min-width: 150px;
max-width: 500px;
width: 100%;
}
.example-full-width {
padding-top: 10px;
width: 100%;
}
<form class="example-form">
<mat-form-field class="example-full-width">
<mat-label>Token</mat-label>
<input
type="text"
placeholder="Select a token"
aria-label="Token name"
matInput
[formControl]="tokenNamesControl"
[matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete">
@for (tokenName of filteredTokenNames | async ; track tokenName) {
<mat-option [value]="tokenName">{{tokenName.displayName}}</mat-option>
}
</mat-autocomplete>
</mat-form-field>
</form>
This results in odd behavior from the matInput where the search in autocomplete works, I can select a object, but the displayed value is [object Object]
. This makes sense, since the underlying selected value from the autocomplete is an object, but I would like the input to have a displayed text of tokenName.displayName
just like my <mat-options>
do.
Is there a way to get the matInput
to display a property of an object selected from autocomplete? Alternatively, is there a better pairing of angular material components I should be using when selecting from an array more complex objects that preserves having working autocomplete?
>Solution :
You need to use the [displayWith]
property to customize the displayed text for the selected option.
displayFn(tokenName: any): string {
return tokenName && tokenName.displayName ? tokenName.displayName : '';
}
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
...
</mat-autocomplete>
Reference: Setting separate control and display values