Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Angular Material mat-autocomplete without ReactiveForm

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.

like image 909
Valentin Coudert Avatar asked Jan 29 '18 14:01

Valentin Coudert


1 Answers

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.

StackBlitz Demo

like image 153
AT82 Avatar answered Sep 17 '22 12:09

AT82