A better way of displaying local loader in Angular application
December 12, 2018
Not so good
displayLoader("Loading...");
this.productsService.getAll().pipe(finalize(() => hideLoader()));
You’ve probably seen the above construct in your project. I’ve been doing the
same either. This way doesn’t look correct to me. Indeed this way of displaying
and hiding loaders produces so-called temporal coupling by separating
display/hide actions and welding hide together with service function making it
impossible to call this.productsService.getAll()
without preliminary calling
displayLoader('loading')
.
The problem turns obvious as soon as the necessity of reusing
this.productsService.getAll()
method in component emerges and we inevitably
wrap the call into separate function:
public getAllProducts() {
displayLoader('Loading...');
this.productsService.getAll()
.pipe(
finalize(() => hideLoader())
).subscribe((products) => {
this.products = products;
});
}
One more drawback of this method is inability of using it with async
pipe and
inevitably forcing developers to manage subscriptions manually.
The better
The better way would be to automatically display loader on every subscription. Unfortunately RxJS does not provide a ready-to-use operator for this.
That’s why we’re going to create our own doOnSubscribe
pipe operator:
// do-on-subscribe.operator.ts
import { defer, Observable } from "rxjs";
export const doOnSubscribe = (onSubscribe: () => void) => <T>(
source: Observable<T>
) =>
defer(() => {
onSubscribe();
return source;
});
After doOnSubscribe
operator is ready we can use it on HttpClient observables:
this.allProducts$ = this.productsService.getAll().pipe(
doOnSubscribe(() => displayLoader("Loading...")),
finalize(() => hideLoader())
);
The above method guaranties loader would be properly displayed and hidden on
every subscription. The newly created “pure” observable could be used with
async
pipe or in combination with other observables:
<div *ngFor="let product of allProducts$ | async">
{{ product.name }}
</div>