Search code examples
angulardebouncingangular-material-5mat-autocomplete

Angular materialAutoComplete field service call pulls data slower than user types in


this.filteredContact = this.contactControl.valueChanges.pipe(
  startWith(''),
  debounceTime(200),
  map(val => this.filterContact(val))
);

filterContact(val: string): any {
  if (val.length > 2) {
    return _.filter(
      this.contacts,
      item => item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1
    );
  }
}

this.filteredContact = this.contactControl.valueChanges.pipe(
  startWith(''),
  debounceTime(200),
  switchMap(
    val =>
    val.length > 2 ?
    _.filter(
      this.contacts,
      item => item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1
    ) :
    Observable.of([])
  ),
  map(result => result.contacts)
);

I am using Angular materialAutoComplete field where service call pulls data as the user keys in. As the user types faster than the service pulls the data, there is no sync in what is actually displayed in the autocompleteSelection panel. How to achieve a perfect data in materialAutoComplete. How to match/overcome the speedness of user tying in the field and the output fetched from service to display in the autocompletePanel?

public filteredCustomersByName: Observable<e.ICustomer[]>;

    this.filteredCustomersByName = this.customerNameControl.valueChanges.pipe(
      startWith(''),
      debounceTime(200),
      map(val => this.filterCustomersByName(val))
    );


  filterCustomersByName(val: string): any {
    if (val.length > 2) {
      this.appData.get(this.appData.url.getCustomerByName, [val]).subscribe(
        result => {
          this.customersByName = result.customers;
        },
        err => {
          console.log('Error ' + err.message);
          this.toastr.error(err.message);
        }
      );
      return this.customersByName;
    }
  }
            <div class="box-row">
              <mat-form-field>
                <input placeholder="Customer Name" type="text" #customerNameId matInput [formControl]="customerNameControl" [(ngModel)]='selectedCustomerName'
                  [matAutocomplete]="customerName" required>
                <mat-autocomplete #customerName="matAutocomplete" class='customerDetails'>
                  <mat-option (click)="setCustomerDetails(customer)" *ngFor="let customer of filteredCustomersByName | async" [value]="customer.name">
                    {{ customer.name}}
                  </mat-option>
                </mat-autocomplete>
              </mat-form-field>
            </div>

Thanks in advance.

 filterCustomerByCode(val: string): any {
    if (val.length > 2) {
      if (val.charAt(0).toLowerCase() !== 'b') {
        val = ('000000000000' + val).substr(-10);
      }
      this.appData.get(this.appData.url.getCustomerByCode, [val]).subscribe(
        result => {
          this.customersByCode = result.customers;
        },
        err => {
          console.log('Error ' + err.message);
          this.toastr.error(err.message);
        }
      );
      return this.customersByCode;
    }
  }

I tried changing the filteredContact using switchMap, please check and let me know why am getting error at result.contacts in the statement map(result => result.contacts)


Solution

  • Try using switchMap. It will make sure that the result is always fresh.

    On each emission the previous inner observable (the result of the function you supplied) is cancelled and the new observable is subscribed

    filteredCustomersByName

    this.filteredCustomersByName = this.customerNameControl.valueChanges.pipe(
      startWith(''),
      debounceTime(200),
      switchMap(val => val.length > 2 
          ? this.appData.get(this.appData.url.getCustomerByName, [val])
          : of([])
     ),
      map(result => result.customers)
    );
    

    filterCustomerByCode

    this.filterCustomerByCode = this.customerNameControl.valueChanges.pipe(
      startWith(''),
      debounceTime(200),
      switchMap((val: string) => {
        if (val.length > 2 || val.charAt(0).toLowerCase() === 'b') {
          const mappedVal = ('000000000000' + val).substr(-10);
          return this.appData.get(this.appData.url.getCustomerByName, [mappedVal]);
        }
        return of([]);
      }),
      map(result => result.customers)
    );