I have this code that searching by a specific word
html:
<input type="input" #term>
<div *ngIf="term.value.length">
<div *ngIf="data?.length !== 0; else no_data">
<div *ngFor="let item of data">
<span>{{ item }}</span>
</div>
</div>
<ng-template #no_data>Not Found</ng-template>
</div>
class:
fromEvent(this.term.nativeElement, 'keyup')
.pipe(
map((event: any) => event.target.value),
switchMap((term) => {
return this.fakeRequest(term);
})
)
.subscribe((data) => {
this.data = data;
});
The problem with this code is ng-template
. Sometime it is displayed just before the actual result appear.
So there is no problems with this.fakeRequest
as per stackbliz
Steps to reproduce:
1 search for 'bla'
2 remove 'bla'
3 type 'al' or 'alpha'
Here is a stackbliz demo
CodePudding user response:
Problem is your fakeRequest
returns result after a delay and till then on ui it displays the already existing result or not found if there is nothing in the list.So the best way will be to add a property like isLoading
in the component set this to true before you make the request and set it to false once request comes back something like
isLoading:boolean;
ngOnInit() {
fromEvent(this.term.nativeElement, 'keyup')
.pipe(
map((event: any) => event.target.value),
switchMap((term) => {
this.isLoading=true;
return this.fakeRequest(term);
})
)
.subscribe((data) => {
this.isLoading=false;
this.data = data;
});
}
then on ui use this property to display a loading block
<div *ngIf="term.value.length">
<div *ngIf="!isLoading; else loading">
<div *ngIf="data?.length !== 0; else no_data">
<div *ngFor="let item of data">
<span>{{ item }}</span>
</div>
</div>
</div>
<ng-template #no_data> Not Found </ng-template>
<ng-template #loading>Loading.....</ng-template>
</div>
CodePudding user response:
Based on the answer of jitender I think the more reactive approach would be to implement the following:
data$: Observable<string[]>;
isLoading$: Observable<boolean>;
keyUp$: Observable<string>;
ngOnInit() {
this.keyUp$ = fromEvent(this.term.nativeElement, 'keyup').pipe(
map((event: any) => event.target.value)
);
this.data$ = this.keyUp$.pipe(
switchMap((term) => {
return this.fakeRequest(term);
}),
shareReplay(1)
);
this.isLoading$ = merge(
this.keyUp$.pipe(mapTo(true)),
this.data$.pipe(mapTo(false))
);
}
and then in your HTML
<input type="input" #term />
<div *ngIf="!(isLoading$ | async); else loading">
<div *ngIf="(data$ | async)?.length !== 0; else no_data">
<div *ngFor="let item of data$ | async">
<span>{{ item }}</span>
</div>
</div>
</div>
<ng-template #no_data> Not Found </ng-template>
<ng-template #loading>Loading.....</ng-template>
This way you have no side effects in your observable pipes and also you don't have to worry about unsubscribing. Of course the loading message is optional but I think the idea should be clear.
Update after feedback from comment The problem with your template approach and using if/else is that you basically want to display three different states:
- data was found
- no data was found
- nothing should be displayed since no input was given
It's hard to achieve this having only one variable, but nevertheless you can use your variable data
to have states accordingly:
- data is array with length > 0
- data is array with length === 0
- data is null
You HTML would look like this:
<input type="input" #term />
<div *ngIf="data !== null && data?.length !== 0">
<div *ngFor="let item of data">
<span>{{ item }}</span>
</div>
</div>
<ng-container *ngIf="data !== null && data?.length === 0">Not Found</ng-container>
and your component.ts would look like this:
@ViewChild('term', { static: true }) term: ElementRef;
data: string[] | null;
ngOnInit() {
fromEvent(this.term.nativeElement, 'keyup')
.pipe(
map((event: any) => event.target.value),
switchMap((term) => {
return this.fakeRequest(term);
})
)
.subscribe((data) => {
console.log(data);
this.data = data;
});
}
fakeRequest(term: string): Observable<string[] | null> {
if (term.length === 0) return of(null);
const getResult = [
'Abarth',
'Alfa Romeo',
'Aston Martin',
'Audi',
'Bentley',
'BMW',
'Bugatti',
'Cadillac',
'Chevrolet',
'Chrysler',
'Citroën',
'Dacia',
'Daewoo',
'Daihatsu',
'Dodge',
'Donkervoort',
'DS',
'Ferrari',
].filter((el) =>
el.toLocaleLowerCase().startsWith(term.toLocaleLowerCase())
);
return of(getResult).pipe(delay(1000));
}
Stackblitz preview: https://stackblitz.com/edit/angular-ivy-dn3j1w?file=src/app/app.component.html