Show activity Indicator while loading a lazy loaded Module in Angular 2

20,794

Solution 1

You can listen for two router events:

  • RouteConfigLoadStart
  • RouteConfigLoadEnd

They fire when a lazy loaded module is being loaded. The advantage of using these over the standard router events such as NavigationStart is that they won't fire on every route change.

Listen to them in your root AppComponent to show / hide your spinner.

app.component.ts

import { Router, RouteConfigLoadStart, RouteConfigLoadEnd } from '@angular/router';

...

export class AppComponent implements OnInit {

    loadingRouteConfig: boolean;

    constructor (private router: Router) {}

    ngOnInit () {
        this.router.events.subscribe(event => {
            if (event instanceof RouteConfigLoadStart) {
                this.loadingRouteConfig = true;
            } else if (event instanceof RouteConfigLoadEnd) {
                this.loadingRouteConfig = false;
            }
        });
    }
}

app.component.html

Just a simple string here, but you could use a spinner component.

<router-outlet></router-outlet>

<ng-container *ngIf="loadingRouteConfig">Loading route config...</ng-container>

I'm using this approach with Angular v4.2.3

Solution 2

you can do it like this

  1. in app.component.html
<div class="main-loader" *ngIf="loading">
  <div class="cssload-container" >
      <div class="cssload-whirlpool"></div>
  </div>
</div>
  1. in app.component.ts

    import { Router, NavigationStart, NavigationEnd } from '@angular/router';
    
    
    loading:boolean = false;
    constructor(private router:Router) { 
      router.events.subscribe(event => {
        if(event instanceof NavigationStart) {
          this.loading = true;
          console.log("event started")
        }else if(event instanceof NavigationEnd) {
          this.loading = false;
          console.log("event end")
        }
        // NavigationEnd
        // NavigationCancel
        // NavigationError
        // RoutesRecognized
      });
    
    }
    
  2. in css any loading animation

hope this is useful to you. thanks

Solution 3

You can just use CSS !

<routler-outlet></routler-outlet>
<div class='.loader>
  Just, wait a sec ! I'm loading
</div>

In your template

router-outlet + .loader {
  opacity : 1;
}

.loader {
  opacity : 0;
}

Then you can create fancy spinners with HTML/CSS

Solution 4

It's possible to click a link to other lazy-loaded route while loading first one. That's why we need to count routes being loaded:

app.component.ts

```

export class AppComponent { 

  currentlyLoadingCount = this._router.events.scan((c, e) => this._countLoads(c, e), 0);

  constructor(private _router: Router) { }

  private _countLoads(counter: number, event: any): number {
    if (event instanceof RouteConfigLoadStart) return counter + 1;
    if (event instanceof RouteConfigLoadEnd) return counter - 1;
    return counter;
  }

```

app.component.html <ng-container *ngIf="currentlyLoadingCount | async">Loading route config...</ng-container>

Share:
20,794
vicmac
Author by

vicmac

Updated on July 09, 2022

Comments

  • vicmac
    vicmac almost 2 years

    My scenario is as follows. I have a menu, with multiple options. Each menu should be shown depending on user permissions (already solved), most menu items are encapsulated as modules, and most of the modules are lazy loaded, so when a user clicks a menu item the first time, it loads (up to here everything works well), now my requirement is, in order to give a better user experience, I need to show activity indicator after user clicks a menu item while the lazy loaded module is loading.

    Up to this, I tried using canActive, canLoad, canActivateChild interfaces from Angular Router but with no luck.

    Any ideas?

  • vicmac
    vicmac about 7 years
    can you elaborate more your answer. I mean, I have a root module, and a feature module to manage all the menus, then when I click in a menu item, it loads a lazy loaded module, so while is loading I would like to show the activity indicator but once the lazy module is loaded, hide the activity indicator. I can't see how to apply this.
  • YounesM
    YounesM about 7 years
    I'm guessing you have a router-outlet to load your component. When the component is not loaded <router-outlet> will be empty. And that will trigger the container:empty + .loader selector and will display your loader. When the data is loaded. <router-outlet> will not be empty anymore, then container:empty + .loaderselector will not be active anymore and your loader will go back to an opacity of 0
  • vicmac
    vicmac about 7 years
    actually yes I have a router-outlet. In fact, I have a app-component with a router-outlet the root outlet and also I have another module which is in charge to manage all the menus, so it has another router-outlet. I just tried your answer putting the class container in my "child router outlet", but it is shown even after module has been loaded.
  • YounesM
    YounesM about 7 years
    @vimac Oops, my bad, I see what was wrong. I was assuming that component load IN router outlet but in fact they load AFTER the outlet. I edited my code. It should be fine now.
  • vicmac
    vicmac about 7 years
    I have a app component with a router-outlet, and an IndexComponent, to show a button to do the authentication by oauth, once the authentication is done user is redirected to IndexComponet again, but this time logged is true, so I detect that and redirects the user to the home page which is anther feature module with a MenuBarComponent to manage all menus and it's template is as follows, the styles are also here inside a style tag. <nav> ... menus goes here </nav> <router-outlet class='container'></router-outlet> <div class='.loader'> Just, wait a sec ! I'm loading </div>
  • YounesM
    YounesM about 7 years
    @vicmac Did you see my last edit ? It should work properly with that.
  • vicmac
    vicmac about 7 years
    yes. I tried it but unfortunatelly is not giving me the results that I want
  • vicmac
    vicmac over 6 years
    I've been out for a while, working on my project. I just resolved with a shared service which is triggered when some http request is started, it solves y problem in part, but I give it a try ASAP. Thanks.
  • Sangwin Gawande
    Sangwin Gawande over 6 years
    You don't need to subscribe twice. Like Daniel Crisp answer, you can do it once. It works and its better to subscribe once.
  • WillyC
    WillyC over 5 years
    This works in that it shows the text, but if you use a spinner or something with CSS animations the animations stop once the module starts to load (although the animation component itself is shown, it is frozen on a frame). Note this isn't a problem with the spinner itself - only when a module is loading.
  • Flo
    Flo over 5 years
    Is there any way to use CSS animations in this scenario?
  • Bart Calixto
    Bart Calixto about 5 years
    you are missing a ' on <div class='.loader>
  • Cito
    Cito almost 5 years
    If you are using the PreloadAllModules strategy, then this would also show all modules loading in the background. In that case, it's best to also catch NavigationStart and NavigationEnd and ignore RouteConfigLoadStart and RouteConfigLoadEnd outside these events.
  • Uriy MerkUriy
    Uriy MerkUriy almost 4 years
    Can add else if (event instanceof NavigationCancel) { this.isLoading = false } for cases when there is a move to another route/module before the finish lazy loading.
  • waternova
    waternova almost 3 years
    If you want a delay before showing the loading state (i.e. only show loading on slow loads), you can use a filter and a switchMap to only set this.loading = true after a delay. Inspired by stackoverflow.com/a/51998101/3370010