Reputation: 779
I am creating a country autocomplete input. I would like to abstract the input into a component using the ControlValueAccessor
interface. I want to use this component to take a FormControl<string | null>
where string is a ISO_3166-2 string and null means no selection.
There are three main parts in the component:
{code: string; name: string;}[]
string | null
FormControl<string>
Currently, when a user selects an autocomplete item it sets the input's FormControl<string>
to the object selected (e.g. {code: 'US', name: 'United States'}
), which obviously is not FormControl<string>
type. Is there a way to handle the autocomplete selection and the input value completely separately?
Here is an example: https://stackblitz.com/edit/angular-ivy-ju6qw9?file=src/app/country-input/country-input.component.ts
In this example, I have used a FormControl<Country>
instead of a FormControl<string>
for the input value, but I am still not getting the desired result because the FormControl is string on runtime after user inputs free text. The filtering functionality is also omitted for simplicity.
Upvotes: 3
Views: 2045
Reputation: 1
I know this is old but this is how I got around it.
The default behavior - in html, assign optionSelected
method to a func:
<mat-autocomplete
...
(optionSelected)="selected($event)">
and in the component's .ts
you reset the input control for the form the matInput
belongs to (assuming you're using one):
selected(event: MatAutocompleteSelectedEvent): void {
//this is the important bit vvvv
this.myFormControl.reset();**
...
//custom logic to handle selected value using event.option.value
...
}
Resetting the form for me instantly got rid of the value. However, if you're using multiple fields, I assume reset gets rid of those fields too, unfortunately.
edit: my whole html code is
<form id="inputForm">
<mat-form-field style="width: 100%">
<mat-label>Add a site...</mat-label>
<input
matInput
[formControl]="myFormControl"
#input
[(ngModel)]="siteInputValue"
(keyup.enter)="add()"
[matAutocomplete]="auto" />
<mat-autocomplete
(opened)="setFilterList()"
#auto="matAutocomplete"
(optionSelected)="selected($event)">
<mat-option
[value]="site.id"
*ngFor="let site of siteInputFilterList | async"
>{{ site.name }}</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>
Upvotes: 0
Reputation: 21
When the input is focused you can change the value before being displayed.
This is not a very good solution, but I hope it helps.
Template:
<mat-form-field>
<input matInput
[matAutocomplete]="auto"
[formControl]="fcInput"
(focus)="onInputFocus()">
<mat-autocomplete #auto>
<mat-option *ngFor="let country of countries" [value]="country">
{{country.name}}
</mat-option>
</mat-autocomplete>
Component:
countries: Country[] = ...;
fcInput = new FormControl<String|null>(null);
onInputFocus(): void {
if (this.fcInput.value instanceof Object) {
let country: Country = <Country>{};
Object.assign(country, this.fcInput.value);
this.fcInput.setValue(country.name);
}
}
Upvotes: 0
Reputation: 61
You can implement your own AutocompleteInputDirective
for the input field of the mat-autocomplete
where you provide the NG_VALUE_ACCESSOR
and the MatFormFieldControl
.
You can then listen to the input
event on the HostElement (the HTMLInput element) but also subscribe to the optionSelected
observable of the matAutocomplete
(which you can require by using a selector like input[myAutocompleteInput][matAutocomplete]
).
Now you are able to differ between user-entered search query values (which can be emitted to a dedicated event emitter) and actually selected option values, which can be emitted to the FormControl (_onChangeFn
from ControlValueAccessor
).
It doesn't matter whether you are using objects as values or specific string values (enums, isoCodes) - only the "actual" values or null will be set to the FormControl.
Additionally the MatFormField
/MatLabel
will correctly show the errorState of the source FormControl
and also add the *
to the label when required.
Usage like this:
<mat-form-field>
<mat-label>My Autocomplete</mat-label>
<input
type="text"
myAutocompleteInput
[formControl]="myFormCtrl"
(query)="filter($event)"
[matAutocomplete]="autocomplete"
/>
<mat-autocomplete #autocomplete="matAutocomplete">...</mat-autocomplete>
</mat-form-field>
the directive ...
EventEmitter
query
for the user search querymyFormCtrl
when an option was selectedmyFormCtrl
when the user changes the input field valuesee the stackblitz - might be helpful
The MatChipGrid
is essentially doing the same to prevent emitting plain user queries. I find it surprising that Angular Material still doesn't offer a such a solution for the MatAutocomplete 🤷♂️.
Upvotes: 2
Reputation: 1161
Is there a way to handle the autocomplete selection and the input value completely separately?
You can use optionSelected
of the mat-autocomplete
. This is an event emitter that will emit only when the option is selected. This allows you to manage when to update the selected
's value.
<mat-form-field>
<input matInput [formControl]="selected" [matAutocomplete]="auto" value=""/>
<mat-autocomplete
#auto="matAutocomplete"
[displayWith]="displayFn"
(optionSelected)="onOptionSelected($event)">
<mat-option *ngFor="let country of countries" [value]="country">
{{country.name}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
@Component({
...
})
export class ExampleComponent {
...
public onOptionSelected(event: MatAutocompleteSelectedEvent) {
console.log(event.option.value);
// do something with the value here.
}
}
Upvotes: 2