2

I have components with loading bar, which shows and hides from boolean variable called isLoading. For example:

ngOnInit() {
  this.isLoading = true;
  this.someService.getAll().subscribe(data => { 
    // some code...
    this.isLoading = false;
  });
}
<loading-bar *ngIf="isLoading"></loading-bar>

But I have a problem, when there are child components in the main component and the data loads from them. The loading bar should be in the main component and should be hide only when data from all child components arrive. What is the best way to achieve this.

zhelyazko
  • 57
  • 1
  • 7

3 Answers3

5

You can use HttpInterceptor to sense any HTTP activity going on in the application and create a service that keep HTTP activity flag inside it. Now in your root component, just check that flag and show a loading spinner. This way you'll not have to individually handle showing of loading bar for each child and sub-child components.

The code for HTTPInterceptor and related service shall be like this.

import { Injectable } from '@angular/core';
import { Observable, of, throwError } from 'rxjs';
import {
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest
} from '@angular/common/http';

import { BehaviorSubject } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';

@Injectable()
export class HTTPStatus {
  private requestInFlight$: BehaviorSubject<boolean>;
  constructor() {
    this.requestInFlight$ = new BehaviorSubject(false);
  }

  setHttpStatus(inFlight: boolean) {
    this.requestInFlight$.next(inFlight);
  }

  getHttpStatus(): Observable<boolean> {
    return this.requestInFlight$.asObservable();
  }
}

@Injectable()
export class HTTPListener implements HttpInterceptor {
  constructor(private status: HTTPStatus) {
  }

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    return next.handle(req).pipe(
      map(event => {
        this.status.setHttpStatus(true);
        return event;
      }),catchError(error => {
        return Observable.throw(error);
      }),
      finalize(() => {
        this.status.setHttpStatus(false);
      })
    )
  }
}

Inject the HTTPStatus service in your root app-component.

  HTTPActivity: boolean;

  constructor(private httpStatus: HTTPStatus) {
    this.httpStatus.getHttpStatus().subscribe(status => { this.HTTPActivity = status; });
  }

In the .html part of your component, now you can show / hide loading bar or spinner by checking the HTTPActivity boolean variable.

<div id="preloader" [hidden]=!HTTPActivity>
  <div id="loader"></div>
</div>

Ofcourse you'll have to register HTTPListener and HTTPStatus service in the providers array of your module.

Thanks.

Obaid
  • 2,563
  • 17
  • 15
1

You can make isLoaded flag for each child component. Then, share the flag state via service to ur parent component. The isLoaded flag of the parent component would be a combo of isLoaded of children

Daniel Prosianikov
  • 100
  • 1
  • 1
  • 10
1

Isn't it the case with the loaders that we put on the entire app itself?

You need to "implement" HttpInterceptor and use the loader in there by attaching both of these with a loader service.

Here's a sample implementation of HttpInterceptor.

@Injectable()
export class LoaderInterceptor implements HttpInterceptor {
    constructor(public loaderService: LoaderService) { }
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        this.loaderService.show();
        return next.handle(req).pipe(
            finalize(() => this.loaderService.hide())
        );
    }
}

You can read more about it here.

You can also go for a counter variable in the LoaderInterceptor above as well. This would clearly be a subject the value of which you would emit to LoaderService (after incrementing/decrementing according to call initiated/completed) where the show variable would turn true when the counter is greater than 0 and vice versa.

Aakash Verma
  • 3,705
  • 5
  • 29
  • 66