How do I show a loading screen when I change a route in Angular 2?
-
http://stackoverflow.com/questions/21512893/angular-js-show-hide-loading-gif-every-new-route – AndrewL64 May 06 '16 at 10:08
-
5That linked question above (AndrewL64's comment) is about AngularJS, not "Angular" (2+). – Gary McGill Jun 06 '19 at 13:42
5 Answers
The current Angular Router provides Navigation Events. You can subscribe to these and make UI changes accordingly. Remember to count in other Events such as NavigationCancel
and NavigationError
to stop your spinner in case router transitions fail.
app.component.ts - your root component
...
import {
Router,
// import as RouterEvent to avoid confusion with the DOM Event
Event as RouterEvent,
NavigationStart,
NavigationEnd,
NavigationCancel,
NavigationError
} from '@angular/router'
@Component({})
export class AppComponent {
// Sets initial value to true to show loading spinner on first load
loading = true
constructor(private router: Router) {
this.router.events.subscribe((e : RouterEvent) => {
this.navigationInterceptor(e);
})
}
// Shows and hides the loading spinner during RouterEvent changes
navigationInterceptor(event: RouterEvent): void {
if (event instanceof NavigationStart) {
this.loading = true
}
if (event instanceof NavigationEnd) {
this.loading = false
}
// Set loading state to false in both of the below events to hide the spinner in case a request fails
if (event instanceof NavigationCancel) {
this.loading = false
}
if (event instanceof NavigationError) {
this.loading = false
}
}
}
app.component.html - your root view
<div class="loading-overlay" *ngIf="loading">
<!-- show something fancy here, here with Angular 2 Material's loading bar or circle -->
<md-progress-bar mode="indeterminate"></md-progress-bar>
</div>
Performance Improved Answer: If you care about performance there is a better method, it is slightly more tedious to implement but the performance improvement will be worth the extra work. Instead of using *ngIf
to conditionally show the spinner, we could leverage Angular's NgZone
and Renderer
to switch on / off the spinner which will bypass Angular's change detection when we change the spinner's state. I found this to make the animation smoother compared to using *ngIf
or an async
pipe.
This is similar to my previous answer with some tweaks:
app.component.ts - your root component
...
import {
Router,
// import as RouterEvent to avoid confusion with the DOM Event
Event as RouterEvent,
NavigationStart,
NavigationEnd,
NavigationCancel,
NavigationError
} from '@angular/router'
import {NgZone, Renderer, ElementRef, ViewChild} from '@angular/core'
@Component({})
export class AppComponent {
// Instead of holding a boolean value for whether the spinner
// should show or not, we store a reference to the spinner element,
// see template snippet below this script
@ViewChild('spinnerElement')
spinnerElement: ElementRef
constructor(private router: Router,
private ngZone: NgZone,
private renderer: Renderer) {
router.events.subscribe(this._navigationInterceptor)
}
// Shows and hides the loading spinner during RouterEvent changes
private _navigationInterceptor(event: RouterEvent): void {
if (event instanceof NavigationStart) {
// We wanna run this function outside of Angular's zone to
// bypass change detection
this.ngZone.runOutsideAngular(() => {
// For simplicity we are going to turn opacity on / off
// you could add/remove a class for more advanced styling
// and enter/leave animation of the spinner
this.renderer.setElementStyle(
this.spinnerElement.nativeElement,
'opacity',
'1'
)
})
}
if (event instanceof NavigationEnd) {
this._hideSpinner()
}
// Set loading state to false in both of the below events to
// hide the spinner in case a request fails
if (event instanceof NavigationCancel) {
this._hideSpinner()
}
if (event instanceof NavigationError) {
this._hideSpinner()
}
}
private _hideSpinner(): void {
// We wanna run this function outside of Angular's zone to
// bypass change detection,
this.ngZone.runOutsideAngular(() => {
// For simplicity we are going to turn opacity on / off
// you could add/remove a class for more advanced styling
// and enter/leave animation of the spinner
this.renderer.setElementStyle(
this.spinnerElement.nativeElement,
'opacity',
'0'
)
})
}
}
app.component.html - your root view
<div class="loading-overlay" #spinnerElement style="opacity: 0;">
<!-- md-spinner is short for <md-progress-circle mode="indeterminate"></md-progress-circle> -->
<md-spinner></md-spinner>
</div>

- 2,555
- 1
- 14
- 32

- 8,446
- 5
- 41
- 54
-
1Great, I appreciate your approach. It has hit me before and I replaced my lengthy method with this appealing idea, than I had to revert becuase I lost control over `router navigation` and `it triggering the spinner` and then not being able to stop it. `navigationInterceptor` seems like a solution but I'm not sure if something else is waiting to break. If mixed with the `async requests` it will introduce the problem again, I think. – Ankit Singh Nov 11 '16 at 06:10
-
@A_Singh did you set the `loading` state to `false` within `NavigationCancel` and `NavigationError` as well? You would want to set the variable to false within the other 2 conditions in case a HTTP request fails(in case of 404s etc.) By the way, I've used this approach in many different projects at our company with no issues at all. You could also use the navigationInterceptor function within components in case you want to manually set which pages need the loading spinner and which don't – borislemke Nov 11 '16 at 06:23
-
@borislemke I did it using the `old-Router`. I haven't tried the new one. I think your `last edit` will solve most of the issues I assumed. :) – Ankit Singh Nov 11 '16 at 11:52
-
This is a good simple solution for navigation, however when the target route/page needs data then you will need to show the spinner again. Is there an event that can fire at navigation level AFTER the target page's OnInit event has completed? (ie. after the target data has been loaded?) – Rodney Nov 28 '16 at 21:15
-
@Rodney you could create a service and connect the above navigationInterceptor with any component through it. Have a look at "component interactions through a service" in this link: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service – borislemke Nov 29 '16 at 05:36
-
@borislemke - The loading div is never getting displayed, but when I set the loading as true it is getting displayed. I have debugged and found that though the loading is set to true the loading div is not getting displayed. Can you tell me the problem. I have tried the similar way as provided in http://stackoverflow.com/questions/38637176/show-load-spinner-on-angular2-route-navigate – suryakiran Jan 02 '17 at 10:18
-
I tried to use this but it doesn't wait css animation.You can't animate before route because it will route immediately – Persk Feb 02 '17 at 03:50
-
great example and very simple , but writing this code in every component dosen't give us modularity can we make a directive out of the same ? – Rahul Singh Feb 10 '17 at 09:27
-
@RahulSingh you don't have to write it in "every" component. Only the root component. – borislemke Feb 10 '17 at 10:27
-
@borislemke so where where we make use of router resolve , it will work there right? one more thing i am trying to use the md-progress-bar of material but it just dosen't show up, how to make that work , i am using webpack angular 2 – Rahul Singh Feb 10 '17 at 12:09
-
1Maybe this worked at some point? It's not working now with Angular 2.4.8. The page is synchronous. The Spinner doesn't get rendered until the whole page/parent component renders, and that the at NavigationEnd. That defeats the point of the spinner – techguy2000 Feb 25 '17 at 04:50
-
1Using opacity is not a great choice. Visually, it's fine, but in Chrome, if the loading image/icon is on top of a button or text, you cannot access the button. I changed it to use `display` either `none` or `inline`. – mellis481 May 11 '17 at 18:38
-
-
-
@im1dermike I said "of the example". If you wanna use display, go ahead. If you want to remove the DOM element, go ahead, if you want to refresh the browser, go ahead. It's an example, not something you HAVE to do. – borislemke May 13 '17 at 03:01
-
2I like this approach, good job man! But I have an issue and I dont understand why, if I wont switch animation on NavigationEnd, I can see the spinner loading, but if I switch to false, routes change so fast so that I cant even see any animations :( I have tried with even network throteling to slow down the connection but its still remains the same :( no loading at all. Could you give me any suggestions on this please. I control the animations by adding and removing class at the loading element. Thanks – d123546 May 23 '17 at 11:11
-
I think this method is only good if you are using lazy loaded Modules or ResolveData to load data on router transitions. If you offload data fetches into components instead of ResolveData within the router, this method is no good. – borislemke May 23 '17 at 11:15
-
-
@Machado this is the "Angular version". If you are talking about AngularJS, then that's out of the questions. They are 2 very different things. – borislemke Feb 06 '18 at 06:37
-
1I got this error when I copied your code `'md-spinner' is not a known element:`. I am quite new to Angular. Could you please tell me what might be the mistake? – Manu Chadha May 23 '18 at 20:41
-
@ManuChadha you might need to use `mat-spinner` instead depending on the material version you're using. Also make sure you have the `@angular/material` package installed – borislemke May 30 '18 at 07:10
-
It's a useful answer as I used the Navigation interception code to show the spinners shown by this npm package: https://www.npmjs.com/package/ngx-loading – johnstaveley Oct 17 '18 at 10:14
-
With `*ngIf` it didn't work (Angular 7), the last performance suggestion works perfectly fine with `mat-progress-bar` – CularBytes Dec 07 '18 at 09:12
-
@borislemke I tried your solution but it doesn't work for me. When I click on link (tag A with routerLink property) my app freeze. Any suggestions? – erikscandola Mar 13 '19 at 15:23
UPDATE:3 Now that I have upgraded to new Router, @borislemke's approach will not work if you use CanDeactivate
guard. I'm degrading to my old method, ie:
this answer
UPDATE2: Router events in new-router look promising and the answer by @borislemke seems to cover the main aspect of spinner implementation, I havent't tested it but I recommend it.
UPDATE1: I wrote this answer in the era of Old-Router
, when there used to be only one event route-changed
notified via router.subscribe()
. I also felt overload of the below approach and tried to do it using only router.subscribe()
, and it backfired because there was no way to detect canceled navigation
. So I had to revert back to lengthy approach(double work).
If you know your way around in Angular2, this is what you'll need
Boot.ts
import {bootstrap} from '@angular/platform-browser-dynamic';
import {MyApp} from 'path/to/MyApp-Component';
import { SpinnerService} from 'path/to/spinner-service';
bootstrap(MyApp, [SpinnerService]);
Root Component- (MyApp)
import { Component } from '@angular/core';
import { SpinnerComponent} from 'path/to/spinner-component';
@Component({
selector: 'my-app',
directives: [SpinnerComponent],
template: `
<spinner-component></spinner-component>
<router-outlet></router-outlet>
`
})
export class MyApp { }
Spinner-Component (will subscribe to Spinner-service to change the value of active accordingly)
import {Component} from '@angular/core';
import { SpinnerService} from 'path/to/spinner-service';
@Component({
selector: 'spinner-component',
'template': '<div *ngIf="active" class="spinner loading"></div>'
})
export class SpinnerComponent {
public active: boolean;
public constructor(spinner: SpinnerService) {
spinner.status.subscribe((status: boolean) => {
this.active = status;
});
}
}
Spinner-Service (bootstrap this service)
Define an observable to be subscribed by spinner-component to change the status on change, and function to know and set the spinner active/inactive.
import {Injectable} from '@angular/core';
import {Subject} from 'rxjs/Subject';
import 'rxjs/add/operator/share';
@Injectable()
export class SpinnerService {
public status: Subject<boolean> = new Subject();
private _active: boolean = false;
public get active(): boolean {
return this._active;
}
public set active(v: boolean) {
this._active = v;
this.status.next(v);
}
public start(): void {
this.active = true;
}
public stop(): void {
this.active = false;
}
}
All Other Routes' Components
(sample):
import { Component} from '@angular/core';
import { SpinnerService} from 'path/to/spinner-service';
@Component({
template: `<div *ngIf="!spinner.active" id="container">Nothing is Loading Now</div>`
})
export class SampleComponent {
constructor(public spinner: SpinnerService){}
ngOnInit(){
this.spinner.stop(); // or do it on some other event eg: when xmlhttp request completes loading data for the component
}
ngOnDestroy(){
this.spinner.start();
}
}

- 8,965
- 6
- 43
- 46

- 24,525
- 11
- 66
- 89
-
[see this](http://stackoverflow.com/questions/34165140/how-does-angular-2-0-for-typescript-alpha-animation-work) also. I don't how much Animation does angular support by default. – Ankit Singh May 06 '16 at 10:48
-
can you help me write up the service for the spinner? I can do the animations and whatnot in CSS myself, but it would be nice if you could help me with the service. – Lucas May 06 '16 at 10:49
-
[see this implemetation](https://manuel-rauber.com/2016/01/05/angular-2-spinner-component/) also, same but not exactly – Ankit Singh May 06 '16 at 10:50
-
1I've added some code for `spinner-service` now you just need to other parts to make it work. And remember it's for `angular2-rc-1` – Ankit Singh May 06 '16 at 10:55
-
is something else needed for the component page on which I want to include the spinner? It's currently not working for me, and I think it's because I need to include the spinner somehow... – Lucas May 06 '16 at 11:14
-
I didn't include any import paths, because that's just redundant without knowing your file structure. Add them as per your file structure – Ankit Singh May 06 '16 at 11:16
-
hi, one more question - can you please check the code for all the other routes' components? how does it get the `SpinnerService` when it's included the `SpinnerComponent`? – Lucas May 06 '16 at 11:30
-
-
Creating an extra service to handle something simple and then adding it everywhere, and implementing ngOnInit and ngONDestroy just to stop and start animation , this is too much / – Milad Dec 26 '16 at 23:56
-
1actually, this is awesome and works, my tip for testing purposes you can delay the stop of spinner with setTimeout(() => this.spinner.stop(), 5000) in ngOnInit – Jhonatas Kleinkauff Mar 26 '17 at 02:19
Why not just using simple css :
<router-outlet></router-outlet>
<div class="loading"></div>
And in your styles :
div.loading{
height: 100px;
background-color: red;
display: none;
}
router-outlet + div.loading{
display: block;
}
Or even we can do this for the first answer:
<router-outlet></router-outlet>
<spinner-component></spinner-component>
And then simply just
spinner-component{
display:none;
}
router-outlet + spinner-component{
display: block;
}
The trick here is, the new routes and components will always appear after router-outlet , so with a simple css selector we can show and hide the loading.

- 27,506
- 11
- 76
- 85
-
doesn't allow us to pass value from component to the parent component , so it will be a bit complex to hide the loading div. – Praveen Rana May 03 '17 at 07:02
-
2Also it can be super annoying if your application has a lot of route changes to see the spinner every single time instantly. It’s better to use RxJs and set a debounced timer so it only appears after a short delay. – Simon_Weaver Feb 17 '19 at 11:07
If you have special logic required for the first route only you can do the following:
AppComponent
loaded = false;
constructor(private router: Router....) {
router.events.pipe(filter(e => e instanceof NavigationEnd), take(1))
.subscribe((e) => {
this.loaded = true;
alert('loaded - this fires only once');
});
I had a need for this to hide my page footer, which was otherwise appearing at the top of the page. Also if you only want a loader for the first page you can use this.

- 1
- 1

- 140,023
- 84
- 646
- 689
You could also use this existing solution. The demo is here. It looks like youtube loading bar. I just found it and added it to my own project.

- 4,675
- 3
- 30
- 38
-
does it help with resolvers? My problem is that while resolvers bring back the data, I am unable to show spinner anywhere because the actual target component ngOninit has not yet been called !! My idea was to show the spinner in ngOnInit and hide it once resolved data is returned from route subscription – kuldeep Aug 05 '19 at 21:32