Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

NgRx + Loading Indicator

Application loading indicator using NgRX and Angular.

Why?

Most applications perform asynchronous loading of data, and it may be necessary for your application to show a loading indicator while the data is being loaded. Using NgRX we can easily toggle the display of a loading indicator or spinner based on the actions that are dispatched to the store.

Sample Application

I’ll be using two branches from a sample app that is a tour of heroes demo application:

  1. ngrx-refactor-2 is the first take
  2. ngrx-refactor-3 is the second take

You can download or clone the ngrx-refactor-2 branch of the repository at:

First Take

First, let’s take a look at an example State interface that includes a loading property. You can see the first take working in the example application’s ngrx-refactor-2 branch:

$ git checkout ngrx-refactor-2
$ cd server && yarn

Here is the trimmed contents of src/app/state/powers/reducers/powers.ts:

export interface State extends EntityState<Power> {
  loading: boolean;
}

export const adapter: EntityAdapter<Power> = createEntityAdapter();

const initialState: State = adapter.getInitialState({
  loading: false
});

export function reducer(state: State = initialState, action: PowersAction) {
  switch (action.type) {
    case LOAD_POWERS:
      return {...state, loading: true};
    case LOAD_POWERS_SUCCESS:
      state = {...state, loading: false};
      return adapter.addAll(action.payload, state);
    default:
      return state;
  }
}

export const isLoading = (state: State) => state.loading;

A few things to note:

We then create a selector for the loading property in the src/app/state/powers/reducers/index.ts file:

export const isPowerLoading = createSelector(
  getPowersEntityState,
  fromPowers.isLoading,
);

In our stateful (container) component we can select this value from the store using the selector. ThiS example is from the src/app/+powers/containers/index/index.component.ts file:

loading: Observable<boolean>;

ngOnInit() {
    this.loading = this.store.pipe(select(isPowerLoading));
}

In the stateless (dumb) component we can then use this value to toggle the display of Material’s spinner component. Here is the input into the component in src/app/+powers/components/powers/powers.component.ts:

export class PowersComponent {
  @Input() loading: boolean;
}

And here is the template in src/app/+powers/components/powers/powers.component.html:

<mat-list *ngIf="!loading; else spinner">
  <!-- list the powers -->
</mat-list>
<ng-template #spinner>
  <div fxLayout="row" fxLayoutAlign="center center">
    <mat-spinner></mat-spinner>
  </div>
</ng-template>

In the template code above we use the NgIf directive to toggle the display of the spinner based on the loading value. If loading is false then we display the list of powers, otherwise, we display the <ng-template> with the spinner in the center.

Second Take

While the first take is a good approach to using NgRX to toggle the display of a loading indicator or spinner, we can refactor this to better use NgRX, specifically, the effects that are triggered by actions in our application.

The solution for the second take is available on the sample application’s ngrx-refactor-3 branch:

$ git checkout ngrx-refactor-3

You can download or clone the ngrx-refactor-3 branch via:

Actions

To get started, let’s create two actions:

  1. ShowSpinner, which will show the spinner.
  2. HideSpinner, which will hide the spinner.

Let’s take a look at the src/app/state/shared/actions/spinner.ts file:

import { Action } from "@ngrx/store";
import { createActionType } from "../utils";

export const SPINNER_SHOW = createActionType("SPINNER_SHOW");
export const SPINNER_HIDE = createActionType("SPINNER_HIDE");

export class HideSpinner implements Action {
  readonly type = SPINNER_HIDE;
}

export class ShowSpinner implements Action {
  readonly type = SPINNER_SHOW;
}

export type SpinnerAction = ShowSpinner | HideSpinner;

To summarize:

Reducer

Next, we’ll define a reducer() function to mutate the state of our application. Let’s take a look at src/app/state/shared/reducers/spinner.ts:

import { SPINNER_HIDE, SPINNER_SHOW, SpinnerAction } from "../actions/spinner";

export interface State {
  show: boolean;
}

const initialState: State = {
  show: false
};

export function reducer(state: State = initialState, action: SpinnerAction) {
  switch (action.type) {
    case SPINNER_HIDE:
      return { ...state, show: false };
    case SPINNER_SHOW:
      return { ...state, show: true };
    default:
      return state;
  }
}

export const isShowing = (state: State) => state.show;

Let’s quickly review:

Don’t forget to add the new feature state to your application’s State. This is located in the src/app/core/state/app.reducer.ts file in the example application:

export const appReducer: ActionReducerMap<AppState> = {
  router: routerReducer,
  snackbar: fromSnackbar.reducer,
  spinner: fromSpinner.reducer
};

I’ve simply added the spinner property with a reference to the reducer() function we just created and exported.

Selector

If you noticed, I defined an isShowing function that accepts the State object and returns the boolean show value in the src/app/state/shared/reducers/spinner.ts file. Using this function we’ll create a new selector in src/app/shared/reducers/index.ts:

import { createSelector, createFeatureSelector } from '@ngrx/store';
import * as fromSpinner from './spinner';

export const selectSpinnerEntity =
  createFeatureSelector < fromSpinner.State > 'spinner';
export const isSpinnerShowing = createSelector(
  selectSpinnerEntity,
  fromSpinner.isShowing,
);

We’ll be using the isSpinnerShowing selector in our application to toggle the display of the loading indicator or spinner.

Effects

With our actions created and our reducer function to mutate the state of our app, we are ready to implement the actions in our effects. In this example, I am only going to update the effects for the powers module. You’ll need to implement the effects in each entity where you are planning to toggle the loading indicator.

Let’s take a look at src/app/state/powers/effects/powers.ts:

type showSpinnerTypes =
  | AddPower
  | DeletePower
  | LoadPower
  | LoadPowers
  | UpdatePower;

const showSpinnerActions = [
  ADD_POWER,
  DELETE_POWER,
  LOAD_POWER,
  LOAD_POWERS,
  UPDATE_POWER
];

type hideSpinnerTypes =
  | AddPowerSuccess
  | DeletePowerSuccess
  | LoadPowersSuccess
  | LoadPowersSuccess
  | UpdatePowerSuccess;

const hideSpinnerActions = [
  ADD_POWER_SUCCESS,
  DELETE_POWER_SUCCESS,
  LOAD_POWER_SUCCESS,
  LOAD_POWERS_SUCCESS,
  UPDATE_POWER_SUCCESS
];

@Injectable()
export class PowersEffects {
  @Effect()
  showSpinner: Observable<Action> = this.actions
    .ofType<showSpinnerTypes>(...showSpinnerActions)
    .pipe(map(() => new ShowSpinner()));

  @Effect()
  hideSpinner: Observable<Action> = this.actions
    .ofType<hideSpinnerTypes>(...hideSpinnerActions)
    .pipe(map(() => new HideSpinner()));
}

Let’s review the code above:

Template

I’m going to add the <mat-spinner> component to the LayoutComponent that I use to wrap my container templates. Where you implement the actual loading indicator or spinner will likely vary based on your application’s architecture. You may also be using a different component from another library or a custom loading indicator component. The goal is the same, toggle the display of the loadig indicator based on the isSpinnerShowing selector.

Let’s look at the example in the src/app/shared/components/layout/layout.component.ts file:

export class LayoutComponent implements OnInit {
  loading: Observable<boolean>;

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

  ngOnInit() {
    this.loading = this.store.pipe(select(isSpinnerShowing));
  }
}

We simply use the select() function, providing the isSpinnerShowing selector, to the store to retrieve the boolean value. I have a loading property that is an Observable of a boolean value, which we will use in the template to toggle the display of the <mat-spinner>.

And, here’s the snippet of the template that toggles the display of the spinner from src/app/shared/components/layout/layout.component.html:

<ng-content *ngIf="!(loading | async); else spinner"></ng-content>
<ng-template #spinner>
  <div fxLayout="row" fxLayoutAlign="center center">
    <mat-spinner></mat-spinner>
  </div>
</ng-template>

In the template we simply use the NgIf directive to toggle the display of the <ng-content> for the application. When we are not loading anything, we’ll display the transcluded content here. When we are loading, we will display the <ng-template> that includes the <mat-spinner> loading indicator.

Conclusion

Using NgRX we can effectively implement an application-wide method for toggling the display of a loading indicator. While we can implement this on a per-component level, we can take advantage of the @ngrx/effects module for dispatching the ShowSpinner and HideSpinner actions based on the actions for adding, deleting, loading and updating an entity.