23

How to add loading spinner in Angular with RxJS

 3 years ago
source link: https://zoaibkhan.com/blog/how-to-add-loading-spinner-in-angular-with-rxjs/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

In this article, we’ll learn how to add a simple loading spinner in Angular using RxJS and then use an HttpInterceptor to automatically show a loader on all network calls in our Angular app.

Why a loading spinner?

But first, why do we need a loading spinner in Angular? Well, in any web app there are times when you need to tell the user that some process is going on. Usually it is network calls, but could be other background work as well. Since you can need it anywhere on the app, we need to have a global way to do it.

In Angular, services provided at the root are the best for this purpose – because they’re instantiated when the app loads and retain their state till the app is destroyed.

Video tutorial

If you’re more of a reader, just continue below 🙂

Setting up the project

Let’s setup our project with the following command.

ng new angular-loading-service

We’ll also be adding the material components in a bit.

Creating a loading service with RxJS

But first, let’s start with creating the service. We’ll use the following command on the Angular CLI.

ng generate service loading

With our new service, let’s declare a BehaviorSubject to contain our loading state.

Subject vs BehaviorSubject in RxJS

A Subject in RxJS is an observable which can have more than one subscribers.

A BehaviorSubject is a special type of Subject, which saves the current value as well and emits it to all its subscribers. In this way, it’s perfect to store some piece of state in our apps.

If you want to learn more about RxJS and the basics, check out my article on creating a simple weather app with RxJS operators!

Our state here will be either showing the loading spinner or hiding it, so we’ll use a boolean and give an initial value of false.

Then, we’ll expose a loading observable so that our parent component can access this state. This is a protection against using the Behavior Subject directly because we want the state to only change from the service methods.

@Injectable({
  providedIn: 'root',
})
export class LoadingService {
  private _loading = new BehaviorSubject<boolean>(false);
  public readonly loading$ = this._loading.asObservable();

  constructor() {}

  show() {
    this._loading.next(true);
  }

  hide() {
    this._loading.next(false);
  }
}
TypeScript

Lastly, we just added two functions, one for hiding and one for showing the loading spinner. We use the next function to set the “current” value of the loader.

And that’s it for the service! Now let’s quickly test this out.

Adding the loading spinner to our Angular app

We’ll first add the angular material library by the following command.

ng add @angular/material

Then, let’s add the necessary imports in our app.module.

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    MatButtonModule,
    MatToolbarModule,
    HttpClientModule,
    MatProgressSpinnerModule,
  ],
  bootstrap: [AppComponent],
})
TypeScript

Now in our app component we’ll create a toolbar and add the progress spinner component and set its mode to indeterminate.

<mat-toolbar color="primary">
    Simple Loading Service with RxJS and HttpInterceptor
</mat-toolbar>
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner>

And also, we’ll add a bit of styling for it to show in the middle of the screen when visible.

mat-progress-spinner {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 5;
}
Sass (Scss)

Let’s test this out now.

Great, we can see a loading spinner at the correct position in the center.

image.png

Now let’s link this up with our loading service. We’ll declare loading$ observable in our component and add our loading service to the constructor. Then, we’ll just equal our variable to the loading observable in our service.

export class AppComponent {
  loading$ = this.loader.loading$;
  constructor(public loader: LoadingService) {}
}
TypeScript

In the template, we’ll add an *ngIf directive with the async pipe, so it shows only when the value is true.

Also, we’ll add simple buttons to show and hide so we can test this out.

<button mat-raised-button (click)="loader.show()">
    Show Loader
</button>

<button mat-raised-button (click)="loader.hide()">
    Hide Loader
</button>

<mat-progress-spinner [mode]="'indeterminate'" *ngIf="loading$ | async"></mat-progress-spinner>

Now, if we test it out, we’ll be able to make the loader appear with the “Show Loader” button and make it disappear with the “Hide Loader” button.

Great! This works well but there’s a slight problem…

Repetitive calls to show and hide the loading spinner

If you have lots of components with API calls in each of them, it becomes a bit repetitive to show and hide the loader before and after calling the API. Since the vast majority of apps need a loader only when making network calls, we can automate that process a bit.

For that, Angular provides a nifty way called HttpInterceptor.

Http Interceptors basically intercept all network calls using the HttpClient in Angular

We can use them for a variety of purposes such as adding an authorization token etc. Here, we can also use it to show and hide the loading spinner automatically whenever an API call is made. This will save us a lot of time and make our code in our components simpler!

So let’s generate an Interceptor using the following command.

ng generate interceptor network

If you look at the interceptor now, we’re given an intercept function, which is called whenever an API call is made from the HttpClient. Let’s include the loading service in the constructor and then show the loader when the intercept function is called.

@Injectable()
export class NetworkInterceptor implements HttpInterceptor {
  constructor(private loader: LoadingService) {}

  intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
    this.loader.show();
    return next.handle(request).pipe(
      finalize(() => {
        this.loader.hide();
      })
    );
  }
}
TypeScript

The end of the API call happens after next.handle, so we’ve added the finalize operator in a pipe after this. The finalize operator in RxJS covers both success and failure cases, so whether the API succeeds or fails, this code should be called. We’re just going to hide the loader here.

Registering the interceptor

The last thing we need to do is to tell Angular about the interceptor. We do this by specifying the interceptor in the HTTP INTERCEPTORS token in the providers for our app in app.module.

providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: NetworkInterceptor,
      multi: true,
    },
 ],
TypeScript

Great, let’s test this out now by making a simple function with a Http call to the Github users API. We’ll only log the output in the console. And we’ll use a button to call this function. If the interceptor is working fine, this should automatically show the loader when network call is in progress.

fetchUser() {
    this.http
      .get('https://api.github.com/users/thisiszoaib')
      .subscribe((res) => {
        console.log(res);
      });
 }
TypeScript

Let’s see if this works.

Nice! So we can see the loader appear and disappear and we also have the output in our console.

Conclusion

So now we have a structure for our apps where we can show a loader automatically when a network call is made and also manually whenever we need it in our components.

Have an idea for making this even better? Share with me in the comments or on twitter and I’ll reply as soon as I can 👍

The complete code for this tutorial can be found here.

Thanks for reading! Bye

If you liked this, follow me on twitter for more!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK