Brian Love
Google Developer Expert in Angular, software engineer and skier located in Denver, CO

NgRx + Loading Indicator

Reading time ~7 minutes

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:

  • First, we defined a loading property in our State interface.
  • Second, we set the value to false in the initialState.
  • In our reducer() function we toggle the value.

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:

  • We define two action type constants: SPINNER_SHOW and SPINNER_HIDE.
  • We then define two action classes that implement the Action interface: HideSpinner and ShowSpinner.
  • Finally, we export the SpinnerAction type that is a union of both the ShowSpinner and HideSpinner types.

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:

  • First, we define a State interface with a single show property that is a boolean value.
  • Next, we set the initialState object with the value of show set to false.
  • In our reducer() function we will return a new copy of the state with the value of show set to true or false.

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:

  • First, I’ve omitted the import statements. You can check out the full example code if you need to reference them.
  • We then create a set of types and actions for when we want to show or hide the spinner.
  • The showSpinnerTypes is a union of all actions for when we will show the spinner.
  • The showSpinnerActions is an array of the action types for when we will show the spinner.
  • The hideSpinnerTypes is a union of all actions for when we will hide the spinner. For the sake of brevity, I am only hiding the spinner when the action is successful. This is obviously not real-world. You’ll want to also include any failure/error actions.
  • The hideSpinnerActions is an array of the action types for when we will hide the spinner.
  • We then define two properties: showSpinner and hideSpinner in the PowersEffects class.
  • The showSpinner property will be triggered when any of the showSpinnerActions are dispatched to the store, and we will dispatch the ShowSpinner action as a result.
  • The hideSpinner property will be triggered when any of the hideSpinnerActions are dispatched to the store, and we will dispatch the HideSpinner action as a result.

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.

Brian Love

Hi, I'm Brian. I am interested in TypeScript, Angular and Node.js. I'm married to my best friend Bonnie, I live in Denver and I ski (a lot).