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';

@NgModule({
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">
<my-app>loading</my-app>
</div>

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';


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

constructor
(
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"
class="form-control"
id="title"
placeholder="Enter title"
formControlName="title">
</div>

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

<button
type="submit"
class="btn btn-primary float-right"
[disabled]="myForm.invalid"
>
<ng-container *ngIf="isSaving; else defaultText">
<div class="loader"></div>
</ng-container>

</button>
</form>

<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),
).subscribe();
}

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