Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a global loader in Angular

Tags:

I have a global loader which is implemented like this:

CoreModule:

router.events.pipe(   filter(x => x instanceof NavigationStart) ).subscribe(() => loaderService.show());  router.events.pipe(   filter(x => x instanceof NavigationEnd || x instanceof NavigationCancel || x instanceof NavigationError) ).subscribe(() => loaderService.hide()); 

LoaderService:

@Injectable({     providedIn: 'root' }) export class LoaderService {      overlayRef: OverlayRef;     componentFactory: ComponentFactory<LoaderComponent>;     componentPortal: ComponentPortal<LoaderComponent>;     componentRef: ComponentRef<LoaderComponent>;      constructor(         private overlay: Overlay,         private componentFactoryResolver: ComponentFactoryResolver     ) {         this.overlayRef = this.overlay.create(             {                 hasBackdrop: true,                 positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically()             }         );          this.componentFactory = this.componentFactoryResolver.resolveComponentFactory(LoaderComponent);          this.componentPortal = new ComponentPortal(this.componentFactory.componentType);     }      show(message?: string) {         this.componentRef = this.overlayRef.attach<LoaderComponent>(this.componentPortal);         this.componentRef.instance.message = message;     }      hide() {         this.overlayRef.detach();     } } 

When running with Angular 7.0.2, the behavior (which I wanted) was:

  • Show loader while resolving data attached to a route, and while loading a lazy module
  • Don't show loader when navigating to a route without any resolver

I have updated to Angular 7.2, now the behavior is:

  • Show loader while resolving data attached to a route, and while loading a lazy module
  • Show the Overlay whithout the LoaderComponent when navigating to a route without any resolver

I have added some logs on the NavigationStart and NavigationEnd events and I found that NavigationEnd is triggered immediately after NavigationStart (which is normal), while Overlay disappears about 0.5s after.

I have read the CHANGELOG.md but I found nothing that might explain this problem. Any idea is welcome.

Edit:

After further research, I have restored the previous behavior by setting package.json like this:

"@angular/cdk": "~7.0.0", "@angular/material": "~7.0.0", 

instead of this:

"@angular/cdk": "~7.2.0", "@angular/material": "~7.2.0", 

I have identified the faulty commit which has been released in version 7.1.0 and I posted my problem on the related GitHub issue. It fixes the fade out animation of the Overlay.

What is the v7.1+ compliant way to get the desired behavior? According to me, the best thing to do would be: show the loader only when necessary, but NavigationStart doesn't hold the needed information. I'd like to avoid ending up with some debounce behavior.

like image 306
Guerric P Avatar asked Jan 10 '19 19:01

Guerric P


People also ask

What are loaders in angular?

Angular loader allows your angular scripts to be loaded in any order. As angular-seed project shows us, Angular loader does not have any specific api, you just put it at the top of your index file (so that it's executed first) and then proceed to load your application files anyway you prefer.


1 Answers

Here is what I ended up with, after realizing that a delay was a good solution in terms of UX because it allowed the loader to show only when the loading time is worth displaying a loader.

I don't like this solution because it implies to share a state between two Observables, when Observables are about pure pipes rather than side effects and shared states.

counter = 0;  router.events.pipe(   filter(x => x instanceof NavigationStart),   delay(200), ).subscribe(() => {   /*   If this condition is true, then the event corresponding to the end of this NavigationStart   has not passed yet so we show the loader   */   if (this.counter === 0) {     loaderService.show();   }   this.counter++; });  router.events.pipe(   filter(x => x instanceof NavigationEnd || x instanceof NavigationCancel || x instanceof NavigationError) ).subscribe(() => {   this.counter--;   loaderService.hide(); }); 
like image 110
Guerric P Avatar answered Sep 21 '22 17:09

Guerric P