r3plica
r3plica

Reputation: 13377

angular 6 show preloader for each component except one

So, I have this Spinner component declared, which is just a simple preloader CSS graphic. It sits on my App component and checks to see if we are loading any htpp data. If we are, it shows the spinner. If we are not, then it hides the spinner:

<div class="spinner-container" *ngIf="loading">
  <div class="spinner">
    <div class="bounce1"></div>
    <div class="bounce2"></div>
    <div></div>
  </div>
</div>

I now have a situation where we don't want to show the spinner at all when on a certain component. I did try to do this using urls:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, NavigationEnd, RoutesRecognized } from '@angular/router';
import { Subscription } from 'rxjs';

import { SpinnerService } from '../services/spinner.service';

@Component({
  selector: 'pyb-spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.scss']
})
export class SpinnerComponent implements OnInit, OnDestroy {
  loading: boolean;

  private widgetUrls: any[] = ['scenarios', 'questions', 'answers', 'results']
  private url: string

  private routeSubscription: Subscription
  private spinnerSubscription: Subscription

  constructor(
    private route: Router,
    private spinnerService: SpinnerService
  ) { }

  ngOnInit() {
    this.routeSubscription = this.route.events.subscribe(event => this.getCurrentUrl(event));
    this.spinnerSubscription = this.spinnerService.onLoadingChanged.subscribe(loading => this.showSpinner(loading));
  }

  ngOnDestroy() {
    if (this.routeSubscription) this.routeSubscription.unsubscribe();
    if (this.spinnerSubscription) this.spinnerSubscription.unsubscribe();
  }

  private showSpinner(loading: boolean) {
    if (this.url) {
      let urlSegments = this.url.split('/');
      if (urlSegments.length > 1) {
        let lastSegment = urlSegments[urlSegments.length - 1];
        let index = this.widgetUrls.indexOf(lastSegment);

        if (index > -1) {
          this.loading = false;
          return;
        }
      }
    }

    this.loading = loading;
  }

  private getCurrentUrl(event: any) {
    if (event instanceof RoutesRecognized) {
      this.url = event.url;
    }
  }
}

But in my case, this won't work because my component has it's routes like this:

const widgetRoutes: Routes = [
  { path: ':category', redirectTo: ':category/scenarios', pathMatch: 'full', data: { state: 'widget' } },
  { path: ':category/:path', component: WidgetComponent, data: { state: 'widget' } }
];

So you can go to /cameras for example and it will show my preloader, but I don't want it to. I could put an exemption in my SpinnerComponent for each and every category, but that seems crazy.

What I would like to do is check the name of the component that is resolved when a route changes and then hide the preloader if it matches my component. Is that possible?

Upvotes: 1

Views: 205

Answers (2)

r3plica
r3plica

Reputation: 13377

I am going to expand on Eliseo's answer. He was right in what he was saying, but it was only a partial answer. The actual answer was to change my ngOnInit to this:

ngOnInit() {
  this.spinnerSubscription = this.router.events
    .pipe(
      filter(event => event instanceof NavigationEnd),
      map(() => this.activatedRoute),
      map(route => route.firstChild),
      switchMap(route => route.data),
      switchMap(data => {
        if (!data.disableSpinner)
          return this.spinnerService.onLoadingChanged
        else 
          return of(false);
      })
    ).subscribe(loading => this.loading = loading);
}

Once this was done, I could use the data in my routes like this:

const routes: Routes = [
  { path: '', redirectTo: '/', pathMatch: 'full' }, // Put first so it redirects :)
  { path: '', component: HomeComponent, data: { state: 'home' } },

  // Lazy load
  { path: '', loadChildren: './widget/widget.module#WidgetModule', data: { state: 'widget', disableSpinner: true } }, // Put this last as it has empty path

  // 404
  { path: '**', component: HomeComponent }, // 404?
]

One thing to note here; if you are using lazy loading, you must make sure the data object is part of the initial declaration. I did have my data object declared in my widget routing module but it was being ignored.

I hope this helps someone else.

PS: To make sure this can actually help someone else; here is my entire component:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';
import { Subscription, of } from 'rxjs';

import { SpinnerService } from '../services/spinner.service';
import { switchMap, filter, map } from 'rxjs/operators';

@Component({
  selector: 'pyb-spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.scss']
})
export class SpinnerComponent implements OnInit, OnDestroy {
  loading: boolean;

  private spinnerSubscription: Subscription

  constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute,
    private spinnerService: SpinnerService
  ) { }

  ngOnInit() {
    this.spinnerSubscription = this.router.events
      .pipe(
        filter(event => event instanceof NavigationEnd),
        map(() => this.activatedRoute),
        map(route => route.firstChild),
        switchMap(route => route.data),
        switchMap(data => {   
          if (!data.disableSpinner)
            return this.spinnerService.onLoadingChanged
          else 
            return of(false);
        })
      ).subscribe(loading => this.loading = loading);
  }

  ngOnDestroy() {
    if (this.spinnerSubscription) this.spinnerSubscription.unsubscribe();
  }
}

Upvotes: 0

Eliseo
Eliseo

Reputation: 57961

You can use the property "data" to add a property "noSpinner" to all the routes you don't need spinner

{ path: ':category/:path', 
  component: WidgetComponent, 
  data: { state: 'widget',noSpinner:true } 
}

If you subscribe to activatedRoute.data, you get the value, If !res.noSpinner, subscribe to onLoadingChange

this.activatedRoute.data.subscribe(res=>{
     console.log(res)
     if (!res.noSpinner)
          this.spinnerSubscription = this.spinnerService.onLoadingChanged
            .subscribe(loading => this.showSpinner(loading));
    })

Well, really you can use switchMap to get only one subscription

this.spinnerSubscription = this.activatedRoute.data.pipe(
     switchMap(res=>{
       console.log(res)
       if (!res.noSpinner)
          return this.spinnerService.onLoadingChanged
       else 
          return of(false);
     }))
     .subscribe(loading => this.showSpinner(loading));

Upvotes: 2

Related Questions