Image Source : https://playtech.ro/2017/cele-mai-mari-turbine-eoliene-iti-alimenteaza-casa-dintr-o-singura-rotatie/

Nowadays, most applications perform asynchronous requests to load data, so in terms of UI experience it’s important to provide feedback to user.

In this article I will present you a solution using NgRx and Angular to toggle the display of a loading indicator based on the actions that are dispatched to the store.

There are many different ways to display a loading indicator using redux and one common approach is to use a simple boolean value on a reducer, but when the application grows up it’s hard to manage it. We can take advantage of the @ngrx/effects module to create a more generic solution.

So, let’s get started!

Action Decorators

Before creating actions, reducers and effects for our loading indicator let’s take a look at two decorators that are used for our NgRx actions that should display a loading indicator.

ShowLoader decorator will add a new boolean property to action class, in order to know that this action should display a loading spinner.

export function ShowLoader() {

return function (Class: Function) {

Object.defineProperty(Class.prototype,'showLoader', {

value: true

});

}

}

HideLoader decorator will add a new string property to action class named triggerAction that will be used into loading indicator reducer to solve the problem when two identical actions was fired but the response came just for the last action. To avoid an infinite loading spinner we will use this property for some checks. We will discuss about this later.

export function HideLoader(triggerAction: string) {

return function (Class: Function) {

Object.defineProperty(Class.prototype, 'triggerAction', {

value: triggerAction

});

}

}

These decorators can be used like in the following example:

import { Action } from '@ngrx/store';

import { ShowLoader, HideLoader } from '../../shared/decorators'; export const GET_ARTICLES = '[Article] Get Articles';

export const GET_ARTICLES_SUCCESS = '[Article] Get Articles success';

export const GET_ARTICLES_FAILURE = '[Article] Get Articles

failure'; @ShowLoader()

export class GetArticles implements Action {

readonly type = GET_ARTICLES;

} @HideLoader( GET_ARTICLES )

export class GetArticlesSuccess implements Action {

readonly type = GET_ARTICLES_SUCCESS;

constructor(public payload?:any) {}

} @HideLoader( GET_ARTICLES )

export class GetArticlesFailure implements Action {

readonly type = GET_ARTICLES_FAILURE;

constructor(public payload?:any) {}

} export type ArticleAction = GetArticles | GetArticlesSuccess | GetArticlesFailure;

Loading Indicator — Actions

Let’s create two actions that will show and hide our loading indicator:

import { Action } from "@ngrx/store";



export const SHOW_SPINNER = '[UI] Show loading spinner';

export const HIDE_SPINNER = '[UI] Hide loading spinner'; export class ShowSpinner implements Action {

readonly type = SHOW_SPINNER; constructor(payload?: any) {} } export class HideSpinner implements Action {

readonly type = HIDE_SPINNER; constructor(payload?: any) {} }



export type SpinnerAction = ShowSpinner | HideSpinner;

Reducer

Next, we’ll define a reducer to mutate the state of our loading indicator.

import * as loadingSpinner from '../actions/loading-spinner'; export interface State {

active: number;

actionsInProgress: any[];

} const initialState: State = {

active: 0,

actionsInProgress: []

} export function reducer(

state = initialState,

action: any): State { switch(action.type) { case loadingSpinner.SHOW_SPINNER: {



const isActionAlreadyInProgress = state.actionsInProgress

.filter((currentAction: any) =>

currentAction === action.payload.type)

.length; // If the action in already in progress and is registered

// we don't modify the state if(isActionAlreadyInProgress) {

return state;

} // Adding the action type in our actionsInProgress array const newActionsInProgress = [

...state.actionsInProgress,

action.payload.type

]; return Object.assign(state, {

active: newActionsInProgress.length,

actionsInProgress: newActionsInProgress

});

} case loadingSpinner.HIDE_SPINNER: { // We remove trigger action from actionsInProgress array const newActionsInProgress = action.payload.triggerAction ?

state.actionsInProgress

.filter((currentAction: any) =>

currentAction !== action.payload.triggerAction) :

state.actionsInProgress;



return Object.assign(state, {

actionsInProgress: newActionsInProgress,

active: state.active > 0 ?

newActionsInProgress.length : 0

});

} default:

return state;

}

} export const isLoadingSpinnerActive =

(state: State) => state.active;

Let’s review our reducer to understand what happens here:

Our state contains two properties:

active — this is the number of actions in progress. active > 0 means that we can display our loading indicator.

actionsInProgress — this is the array that holds all the actions that should display a loading indicator.

The payload for ShowSpinner and HideSpinner actions will be another actions like ShowArticles, ShowArticlesSuccess, ShowArticlesFailure.

So, we can access the properties added by our decorators ( triggerAction ) to check if we should mutate the state or not.

Selector

To access active value from the reducer we need to create a selector for this.

import { createSelector, createFeatureSelector } from "@ngrx/store"; import * as fromSpinner from './reducers/loading-spinner'; export interface State {

loading: fromSpinner.State

} export const reducers = {

loading: fromSpinner.reducer

} export const getLoadingState = (state: State) => state.loading; export const isLoadingSpinnerActive = createSelector(

getLoadingState,

fromSpinner.isLoadingSpinnerActive

);

Effects

Once we created our actions and reducer we are ready to implement the effect service that will trigger ShowSpinner and HideSpinner actions.

import {Injectable} from "@angular/core";

import {Actions, Effect} from "@ngrx/effects";

import {filter, map} from "rxjs/operators"; @Injectable()

export class LoadingIndicatorEffects {

constructor(private actions$: Actions) {}



@Effect()

showLoader$ = this.actions$.pipe(

filter((action: any) => action && action.displayLoader ?

action : null),

map((action: any) => new ShowSpinner(action))

);



@Effect()

hideLoader$ = this.actions$.pipe(

filter((action: any) => action && action.triggerAction ?

action : null),

map((action: any) => new HideSpinner(action))

);

}

showLoader$ will check all actions and will trigger ShowSpinner if the action contains displayLoader property.

hideLoader$ will trigger HideSpinner if the action contains triggerAction property.

Template

Now, you can create a loading component that subscribes to isLoadingSpinnerActive selector and display it a visual animation into user interface.

export class LoadingComponent implements OnInit {

isLoading: Observable<boolean>;



constructor(private store: Store<AppState>) {}



ngOnInit() {

this.isLoading = this.store.pipe(

select(isLoadingSpinnerActive)

);

}

}

And here is the snippet of the loading component template:

<ng-content *ngIf="!(isLoading | async); else spinner"></ng-content>

<ng-template #spinner>

<!-- YOUR ANIMATION / SPINNER HERE -->

</ng-template>

Conclusion

In this article, I took advantage of the @ngrx/effects to solve the task to display globally a loading indicator and it worked for me. But this is not the single solution, here are described another techniques that can be taken into consideration.

DEMO