A better way to handle spinner in Angular with RxJS


In any application now days, every time a Client want to make a request to Backend service, we would like to have a spinner to visualize to our user. But why do we have to make a spinner?

You can find the answer in RAIL Model,

Lets jump straight to the implementation,

  1. Import HttpClientModule, and ReactiveFormsModule into your Module, so that we can make a http request and Reactive Form.
// app.module.tsimport { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule , ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';

imports: [ BrowserModule, FormsModule, HttpClientModule, ReactiveFormsModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
export class AppModule { }

2. Include Bootstrap 4 CDN to index.html.

// index.html<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"><div class="container">

3. In our component class, create a properties that manage the state of spinner. Usually I will name the properties prefix with is-, because it is a boolean type. But you can go anything you like. After that we will create a Reactive form with title and body properties.

// app.component.tsimport { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { HttpClient } from '@angular/common/http';

selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
export class AppComponent {
public myForm: FormGroup;
public isSaving = false

private http: HttpClient,
private formBuilder: FormBuilder,
) {
this.myForm = this.formBuilder.group({
title: ['', Validators.required],
body: ['', Validators.required]


4. And last but not least, in our .html file.

// app.component.html<form novalidate [formGroup]="myForm" (submit)="onSubmit()">
<div class="form-group">
<label for="title">Title: </label>
<input type="text"
placeholder="Enter title"

<div class="form-group">
<label for="body">Body: </label>
<textarea type="text"
placeholder="Enter any message"

class="btn btn-primary float-right"
<ng-container *ngIf="isSaving; else defaultText">
<div class="loader"></div>


<ng-template #defaultText>Submit</ng-template>

If you look at child of <button> you will notice that there is *ngIf. If isSavingis set to true, it will render its child which is <div class="loader"></div>, otherwise it will render defaultText template which is Submit .

We have one last things to do which is how to handle when user submit the form

// inside app.component.ts// import finalize at the top of your file
import { finalize } from 'rxjs/operators';
...onSubmit() {
this.isSaving = true;
// dummy link to post
const url = 'https://jsonplaceholder.typicode.com/fakeapiroute';

this.http.post(url, this.myForm.value).pipe(
finalize(() => this.isSaving = false),

So every time user press submit we will run onSubmit() , we will set the isSaving=true and when the request is done, finalize() will be called and we set back isSaving=false.

I created and example in Stackblitz, so you can play around

Thats all. 😛




Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store


More from Medium

Selection list page

How to share data between Angular components.

Angular String/Text Interpolation

Lesson 15 : Dynamic route and ActivatedRoute Observable