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.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With