I try to use <mat-autocomplete>
from Angular Material (not AngularJS) without using a reactive form. But all their examples use reactive forms...
What I try to do:
1. when something is typed in the mat-input
, make an Ajax call to retrieve a list of users
2. Display the list of users in the autocomplete (display the user's name), but store the user as the model
3. When the model changes, call a function of my choice
For now I do those crazy things (I say crazy because it seems to much).
<mat-form-field fxLayout>
<input type="text"
matInput fxFlex="100"
[(ngModel)]="listFilterValue"
(keyup)="memberInputChanged(input.value)"
(change)="memberChanged()"
*ngIf="isAutocompleteNeeded()"
#input
[matAutocomplete]="auto">
</mat-form-field>
<mat-autocomplete #auto="matAutocomplete" [displayWith]="getMemberAsStr">
<mat-option *ngFor="let member of members | async" [value]="member">
{{ getMemberAsStr(member) }}
</mat-option>
</mat-autocomplete>
For now, there are only console.log
in my JS to see what's called, with what value so I don't share it here. Am I using the right attributes, the right logic ?
(the members
property in my component is a Rxjs BehaviourSubject)
What I do for now does not work because listFilterValue
is never set to anything.
You should avoid calling methods in the template, this could potentially crash your browser as they are called on each change detection, see: *ngFor running an infinite loop in angular2 Technically it's not an infinite loop, but you get the point :)
As for not having form control with the autocomplete is not much different, you just swap the form control to a variable instead, you could use a template driven form if you want, or not a form at all. Here's with template driven form though:
The demo JSON used in this looks like:
"value": [
{
"id": 409,
"joke": "some joke here",
"categories": []
}
]
<form #f="ngForm">
<mat-form-field>
<input matInput [matAutocomplete]="auto"
name="joke" #jokeField="ngModel"
[(ngModel)]="currentJoke" (ngModelChange)="doFilter()">
</mat-form-field>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let joke of jokes | async" [value]="joke.joke">
{{joke.joke}}
</mat-option>
</mat-autocomplete>
</form>
And your TS could look like:
doFilter() {
this.jokes = this.service.getData()
.pipe(
map(jokes => this.filter(jokes.value)),
)
}
filter(values) {
return values.filter(joke =>
// used 'includes' here for demo, you'd want to probably use 'indexOf'
joke.joke.toLowerCase().includes(this.currentJoke))
}
The Service would then have a variable, where we store the api data after first fetch, so that we won't call the api on each key stroke. When data is returned, we check if the variable has been populated, if so, we return an observable of that:
jokes = [];
getData() {
return this.jokes.length ? of(this.jokes)
: this.httpClient.get<any>('https://api.icndb.com/jokes/random/5').pipe(
map((data) => {
this.jokes = data.value;
return this.jokes;
})
)
}
Remember to clear the jokes
array if navigating away.