Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Material, Autocomplete loading indicator and showing text (no results found)

I need to show loading when searching for data and also show text when no record is found.

I have the following html excerpt:

<input
    matInput
    [matAutocomplete]="auto"
    [formControl]="formControl"
    [formlyAttributes]="field"
    [placeholder]="to.placeholder"
    [errorStateMatcher]="errorStateMatcher"
/>

<mat-icon class="arrow-autocomplete">arrow_drop_down</mat-icon>

<mat-autocomplete
    #auto="matAutocomplete"
    [displayWith]="displayFn.bind(this)">

    <mat-option *ngIf="isLoading" class="is-loading">
        <mat-spinner diameter="25"></mat-spinner>
    </mat-option>

    <ng-container *ngIf="!isLoading">
        <mat-option *ngFor="let value of result$ | async" [value]="value">
            <ng-container *ngFor="let ky of to.filterKeys; let index = index">
                {{
                index !== to.filterKeys.length - 1
                    ? value[ky] + ' - '
                    : value[ky]
                }}
            </ng-container>
            <ng-container *ngIf="!to.filterKeys">
                {{ value }}
            </ng-container>
        </mat-option>
    </ng-container>

</mat-autocomplete>

Component.ts code:

ngOnInit(): void {
        super.ngOnInit();

        this._unsubscribeAll = new Subject();
        this.isLoading = false;

        this.result$ = this.formControl.valueChanges.pipe(
            takeUntil(this._unsubscribeAll),
            debounceTime(300),
            startWith(''),
            tap(() => {
                this.isLoading = true
            }),
            switchMap(term => this.to.filter(term))
        );
    }

for loading, I would control it with the tap() method, but my problem is how to know that the request inside switchMap(term => this.to.filter(term)) was finished, so I set the loading variable to false?

my second problem is how to show a registration message not found, when the server returns an empty array, because I am working with async.

like image 492
Glauber Funez Avatar asked Feb 12 '20 16:02

Glauber Funez


People also ask

How do I use angular material autocomplete?

Simple autocompleteStart by creating the autocomplete panel and the options displayed inside it. Each option should be defined by a mat-option tag. Set each option's value property to whatever you'd like the value of the text input to be when that option is selected.

How do you clear mat autocomplete when no option is selected from autocomplete dropdown?

You can remove the formControl-binding from your input and when you select an option you set that id to your form. You are already calling such a function (onSelectionChange)="onEnteredAccount(accountOption)" in which you can do that.

What is Matautocomplete in angular?

The <mat-autocomplete>, an Angular Directive, is used as a special input control with an inbuilt dropdown to show all possible matches to a custom query.


1 Answers

I did something similar to this once, and I've tried to kind of adapt it to what you need so here goes.

Starting with the service, we need to set up a loading stream to watch.

// Loading stream
  private readonly loading = new Subject<boolean>();
  get loading$(): Observable<boolean> {
    return this.loading;
  }


  constructor(private http: HttpClient) {}

  get(q: string) {
    return this.http.get<IResults>(URL + q).pipe(
      // Set loading to true when request begins
      tap(() => this.loading.next(true)),
      // If we get to this point, we know we got the data,
      // set loading to false, return only the items
      map((res) => {
        this.loading.next(false);
        return res.items;
      })
    )
  }

  // Cleanup.
  ngOnDestroy() {
    this.loading.unsubscribe();
  }

In this example the data expected is

interface IResults {
  total_count: number,
  incomplete_results: boolean,
  items: []
}

So we used tap to let everyone know it's loading, then let everyone know we're done in the map(). This solves half the struggles.

Next, the component.ts is pretty simple, we're just watching observables.

  // Form
  searchForm = new FormGroup({
    query: new FormControl('')
  })

  // Get loading stream from service
  loading$: Observable<boolean> = this.gs.loading$; 

  // Deconstruct form to just take the query
  // Search on changes
  searchResults$ = this.searchForm.valueChanges.pipe(
    switchMap(({query}) => this.gs.get(query))
  );

  constructor(private gs: GithubService) { }

Now we can complete this in the html.

<!-- ngIf so my http request is only called once-->
        <form [formGroup]="searchForm" 
            *ngIf="{results: searchResults$ | async, loading: loading$ | async} as obs">
<!-- Search input -->
            <mat-form-field appearance="legacy">
                <input matInput [matAutocomplete]="autoComplete" formControlName="query">
                <mat-icon matSuffix>arrow_drop_down</mat-icon>
                <mat-hint>Search Github Users</mat-hint>
              </mat-form-field>
<!-- Auto Complete -->
            <mat-autocomplete #autoComplete="matAutocomplete">
                <!-- If we're loading -->
                <mat-option disabled class="loading" *ngIf="obs.loading">
                    <mat-spinner diameter="35"></mat-spinner>
                </mat-option>
                <!-- If we're not loading AND the array length is 0, show this -->
                <mat-option disabled *ngIf="obs.results?.length === 0 && !obs.loading">
                    No user found
                </mat-option>
                <!-- Actual payload -->
                <ng-container *ngIf="!obs.loading">
                    <mat-option *ngFor="let result of obs.results"> 
                        {{result.login}} 
                    </mat-option>
                </ng-container>
            </mat-autocomplete>
        </form>

We put the ngif in the form in order to avoid making multiple GET requests in the same component and put it in an object we can reference. Hook up the autocomplete to the input. Now we can add a couple 's that are conditional (length of array and if we are loading).

I also created a stackblitz to test this all out. Pretty fun!
https://stackblitz.com/github/baxelson12/ng-mat-autocomplete

like image 156
Brad Axe Avatar answered Sep 24 '22 06:09

Brad Axe