1

I'm using Angular 4 and I'd like to display a loading screen (basic spinner) between routes navigation. I've used this technique in order to subscribe to the proper events, and used style.visibility in order to show/hide my spinner.

For some reason, although I'm correctly detecting the start and end events of the navigation, my spinner will not show. In fact, although the visibility field is changing correctly, the spinner will still not show.

The only way I managed to display it is by forcing it to be set to 'visible' instead of 'hidden' at all times.

Attached are relevant sections of my code:

app.component.ts:

constructor (private utils: UtilsService, private router: Router) {
  // Intercept all navigation events
  router.events.subscribe((event: RouterEvent) => {
   this.navigationInterceptor(event)
  })

  // Calls our initializer
  utils.init();
 }

 /* Intercept the navigator in order to display a loading screen */
 navigationInterceptor(event: RouterEvent): void {
  // Starting the navigation
  if (event instanceof NavigationStart) {
   this.loading = true;
  }

  // The navigation ended
  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;
  }

  // Get our loading container
  var loading_container = document.getElementById('loader-container');

  // If our loading container isn't available, this is the application first launch (ie. full refresh)
  // so we need to display a full loading logo
  if (loading_container) {
   // This is just a redirect, not a full refresh, so just hide or show the loading screen
   document.getElementById('loader-container').style.visibility = this.loading ? 'visible' : 'hidden';
  }
 }

app.component.html:

<div style="position: fixed; left: 220px; width: calc(100% - 220px); height: 100%; z-index: 10000; background-color: rgba(255, 255, 255, 0.6);" id = "loader-container">
 <div style = "top: 30%;" class="sk-spinner sk-spinner-double-bounce">
  <div class="sk-double-bounce1"></div>
  <div class="sk-double-bounce2"></div>
 </div>
</div>
<!-- Main view/routes wrapper-->
<router-outlet>
</router-outlet>
Walter White
  • 126
  • 1
  • 9

2 Answers2

0

The problem is that you are trying to use native javascript in Angular in Constructor and by that time the DOM is not yet ready. You should use it in AfterViewInit.

In this case you can have a loading variable and that all you need, use it in template to show and close spinner using ngIf depending upon the navigation.

<div *ngIf = "loading" class = "main">
  <div class="spinner">
    <div class="rect1"></div>
    <div class="rect2"></div>
    <div class="rect3"></div>
    <div class="rect4"></div>
    <div class="rect5"></div>
  </div>

CSS

.spinner > div {
  background-color: black;
  height: 100%;
  width: 6px;
  display: inline-block;

  -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
  animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

.spinner .rect2 {
  -webkit-animation-delay: -1.1s;
  animation-delay: -1.1s;
}

.spinner .rect3 {
  -webkit-animation-delay: -1.0s;
  animation-delay: -1.0s;
}

.spinner .rect4 {
  -webkit-animation-delay: -0.9s;
  animation-delay: -0.9s;
}

.spinner .rect5 {
  -webkit-animation-delay: -0.8s;
  animation-delay: -0.8s;
}

@-webkit-keyframes sk-stretchdelay {
  0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
  20% { -webkit-transform: scaleY(1.0) }
}

@keyframes sk-stretchdelay {
  0%, 40%, 100% {
    transform: scaleY(0.4);
    -webkit-transform: scaleY(0.4);
  }  20% {
       transform: scaleY(1.0);
       -webkit-transform: scaleY(1.0);
     }
}

Working example link and the git repo LINK

Rahul Singh
  • 19,030
  • 11
  • 64
  • 86
  • Thanks Rahul. I've used *ngIf but it still doesn't work. I've added "setTimeout" to the NavigationEnd condition and now the spinner shows for a brief time only **after** the navigation completed. I'm trying to show the spinner at the current page, while the other page is loading. Any thoughts on why that might happen? – Walter White Aug 28 '17 at 15:52
  • @WalterWhite then you might need to add router resolve to it also as you want to show component whendata is fully ready that link uses router resolve too check that. Its showing means it works only that the router routes without your component having data – Rahul Singh Aug 28 '17 at 16:15
0

I've solved the problem. The issue was that in JS DOM changes will execute only after the current code finished execution, thus delaying the appearance/disappearance of my spinner to the very end of the code execution. This obviously resulted in it not showing at all, as the moment the code has finished executing it obviously already triggered the NavigationEnd event.

I'm not sure if it's the correct solution, by I ended up using an onClick event on all of my navigation links:

<a (click)="routeClick('/Home')"><i class="fa fa-th-large"></i> <span class="nav-label">Home</span></a>

And created the routeClick() function as follows:

public routeClick(href : string) : void {
    // Display the loading spinner
    UtilsService.loading = true;

    // Redirect to the new route asynchronously (IMPORTANT! Otherwise the spinner will not show!)
    setTimeout('location.href = "#' + href + '";', 1);
  }

Which caused all DOM changes to be performed before the actual navigation began.

Hope this helps!

Walter White
  • 126
  • 1
  • 9