36

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?

MLavoie
  • 8,799
  • 39
  • 36
  • 51
vicmac
  • 565
  • 1
  • 5
  • 10

5 Answers5

81

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

Daniel Crisp
  • 1,787
  • 1
  • 12
  • 23
  • 3
    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. – WillyC Oct 15 '18 at 17:40
  • Is there any way to use CSS animations in this scenario? – Florian Ludewig Nov 21 '18 at 18:30
  • 1
    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. – Cito Jun 16 '19 at 15:07
  • 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. – Uriy MerkUriy May 20 '20 at 07:01
27

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

tanveer ahmad dar
  • 2,984
  • 1
  • 24
  • 29
1

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

YounesM
  • 2,111
  • 11
  • 27
  • 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. – vicmac Mar 23 '17 at 16:19
  • I'm guessing you have a router-outlet to load your component. When the component is not loaded `` will be empty. And that will trigger the `container:empty + .loader` selector and will display your loader. When the data is loaded. will not be empty anymore, then `container:empty + .loader`selector will not be active anymore and your loader will go back to an `opacity` of 0 – YounesM Mar 23 '17 at 16:24
  • 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. – vicmac Mar 23 '17 at 17:01
  • @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. – YounesM Mar 23 '17 at 17:17
  • 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.
    Just, wait a sec ! I'm loading
    – vicmac Mar 23 '17 at 17:37
  • @vicmac Did you see my last edit ? It should work properly with that. – YounesM Mar 23 '17 at 17:39
  • yes. I tried it but unfortunatelly is not giving me the results that I want – vicmac Mar 23 '17 at 18:15
  • you are missing a `'` on `
    – Bart Calixto Apr 29 '19 at 13:24
1

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>

Azargoth
  • 337
  • 1
  • 7
  • 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. – vicmac Nov 12 '17 at 19:18
0

for those who need compatibility (and deal) with IE11, the accepted answer is not working, cause IE11 doesn't render the loader when switching from one module to another, so I did a (not very elegant but working) workaround:

In my menuItem switch click event:

  public navigate(url: string) {
    this.configurationService.loading = true; //service variable linked with html loader

    //let's give time to tortoise IE11 to render loader before navigation...
    let obj = this;
    setTimeout(function() 
    {
      obj.router.navigateByUrl(url);
    }, 1);
  }
LeonardoX
  • 688
  • 8
  • 24