Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Material Autocomplete observable fires additional time after selecting dropdown option

Anytime I select an option from Material Autocomplete, it makes an additional HTTP GET call to the server. The desired result of selecting a dropdown option should just be to populate the input.

The dropdown options are retrieved dynamically from the server.

HTML:

<form class="example-form">
    <mat-form-field class="example-full-width">
      <input type="text" placeholder="search item" aria-label="Item1" matInput [formControl]="searchTerm" [matAutocomplete]="auto1">
      <mat-autocomplete #auto1="matAutocomplete" [displayWith]="displayFn">
        <mat-option *ngFor="let item of searchResult" [value]="item.Id">
          {{ item.Name }}
        </mat-option>
      </mat-autocomplete>
    </mat-form-field>
  </form>

TypeScript:

    searchTerm : FormControl = new FormControl();

    searchResult = [];
    constructor(private service: AppService){
      this.searchTerm.valueChanges
          .debounceTime(400) 
          .subscribe(data => {
              this.service.search_word(data).subscribe(response =>{
                  this.searchResult = response
              })

Everything else works fine. The only issue is the unexpected extra call to the server when the autocomplete option is selected.

QUICK UPDATE: No answer has totally solved this yet. However, I have narrowed this down to possibly be a problem with displayWith.

like image 701
Chemdream Avatar asked Dec 06 '17 21:12

Chemdream


4 Answers

Your issue is occurring because the dropdown selection changes the value of the searchTerm form control; thus emitting an event for the valueChanges observable on that form control.

There are a few ways to build some logic into your valueChanges observable on the form control in order to ignore this unwanted Http GET request.

Naive solution

The naive solution is to store the dropdown option selection value and skip the observable logic if the selectionValue === searchTerm.value.

var optionSelection; // set this value on selection of a dropdown option
this.searchTerm.valueChanges
  .debounceTime(400) 
  .subscribe(data => {
    if (optionSelection !== searchTerm.value) {
      this.service.search_word(data).subscribe(response =>{
        this.searchResult = response
      })
    }
  })

Better Solution

The FormControl class has a setValue function, which has an optional parameter to specify whether or not to emit an event to the valueChanges observable.

setValue(value: any, options: {
    onlySelf?: boolean;
    emitEvent?: boolean;
    emitModelToViewChange?: boolean;
    emitViewToModelChange?: boolean;
} = {}): void

By changing your dropdown option selection to set the searchTerm form control value with setValue, you can pass the optional emitEvent: false parameter and stop this additional observable event from emitting.

searchTerm.setValue(dropdownValue, { emitEvent: false });

If emitEvent is false, this change will cause a valueChanges event on the FormControl to not be emitted. This defaults to true (as it falls through to updateValueAndValidity).

like image 184
Trent Avatar answered Oct 03 '22 08:10

Trent


The problem lies in the amount of event that are tiggered there are two... I already made a bug for it's but it's how it supposed to work. Hope this will help.

This is my solution:

Html:

<mat-option *ngFor="let item of searchResult" [value]="item.Id" (onSelectionChange)="onSelectionChange($event, item.Id)"> // or item

In your component:

private onSelectionChange(_event: any, _id: any) {
  if (_event.isUserInput === true) { // there are two events one with true and one with false;
    this.service.search_word(_id).subscribe(response =>{
         this.searchResult = response
    })
}
like image 36
Swoox Avatar answered Oct 03 '22 08:10

Swoox


you can use (keyup)="onKeyUp($event)" in your input text and you can use it like this

onKeyUp(event: any) {
   this.service.search_word(event.target.value).subscribe(response =>{
                  this.searchResult = response
              })
}
like image 29
anode7 Avatar answered Oct 03 '22 09:10

anode7


As of Angular Material version 7.2.0 you have a new EventEmitter called 'optionSelected', for MatAutocomplete

So your HTML would be something like this:

<mat-autocomplete (optionSelected)="optionSelected($event)" #auto1="matAutocomplete" [displayWith]="displayFn">
    <mat-option *ngFor="let item of searchResult" [value]="item.Id">
      {{ item.Name }}
    </mat-option>
  </mat-autocomplete>

And your .ts:

private optionSelected(event) {
  console.log(event)
}

And you would handle it inside of optionSelected() method.

Give it a try as I don't have all your code. And let me know if it doesn't work.

like image 20
carkod Avatar answered Oct 03 '22 09:10

carkod