How We Are Using NgRx?

Looking back at the last few months, there hasn’t passed a working day that I didn’t work with NgRx. In the following post, I will write about how we are using NgRx in Notch. The goal of this post isn’t to find out what NgRx is, but rather to share our personal experience while using NgRx.
I’m part of a team developing a business application using Angular 6. We can claim that our application performance has been greatly improved by using state management.

I’ll create a simple application and provide a link to my GitHub repository, so you can just clone and play with the code.
Architecture

This is a basic schema for NgRx flow in our architecture. We define state inside of every feature module. Every model in diagram (service, effect, reducer etc.) has its own task that it is responsible for. In most cases, everything starts in a smart component, where we are dispatching specific action.
Effect gets triggered by dispatching action, which is due to some effects that needs to be called before reducer. In 90% cases we are calling REST service from effects. Then we are using received data for dispatching new action to save new data to store.
With this new action, that is dispatched in reducer, we are saving new and previous state. When the state is saved in store, we can then subscribe to the property selector in the smart component. After that, we are passing that result to view component through input method.
Example: How to use NgRx
In these few short sentences, I explained how NgRx works in our applications. Now I’ll setup a simple application. First, a project will be create with using Angular CLI:
ng new ngrx-example
Second step is installing NgRx library. Below is a command for installing library:
npm install @ngrx/core @ngrx/store @ngrx/effects @ngrx/store-devtools @ngrx/router-store --save
After installation, the NgRx ecosystem is ready to use. Next step is importing StoreModule and EffectsModule in AppModule.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { routerReducer } from '@ngrx/router-store';
import { EffectsModule } from '@ngrx/effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
import { RouterModule } from '@angular/router';
import { metaReducers } from './app.reducer';
import { ClubsModule, mainRoutes } from './clubs/clubs.module';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule,
ClubsModule,
StoreModule.forRoot(
{
routerReducer
},
{
metaReducers
}
),
EffectsModule.forRoot([]),
StoreRouterConnectingModule.forRoot({ stateKey: 'router' }),
RouterModule.forRoot(mainRoutes, { useHash: true })
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
Before starting with developing, it’s recommended to setup a meta-reducer for logging every action. Next step is installing ngrx-store-freeze:
npm i --save-dev ngrx-store-freeze
When installation of ngrx-store-freeze is done, it’s necessary to create file app.reducer.ts. Also, it is mandatory to create a debugMetaReducer function that will log old state, action that is dispatched and new state.
import { Action, ActionReducer, MetaReducer } from '@ngrx/store';
import { environment } from 'src/environments/environment';
import { storeFreeze } from 'ngrx-store-freeze';
export function debugMetaReducer(
reducer: ActionReducer<object>
): ActionReducer<object> {
return function(oldState: object, action: Action): object {
const newState = reducer(oldState, action);
console.groupCollapsed(
`%c NgRx store update by '${action.type}'`,
'color: #e2001a'
);
console.log('Old state: ', oldState);
console.log('Action: ', action);
console.log('New state: ', newState);
console.groupEnd();
return newState;
};
}
export const metaReducers: MetaReducer<any>[] = [debugMetaReducer, storeFreeze];
Meta-reducers
Developers can think of meta-reducers as hooks into the action->reducer pipeline. Meta-reducers allow developers to pre-process actions before normal reducers are invoked.
Application structure
In this simple application, one extra module will be created. That module will have store folder, service, smart component and view component. In store folder will be five files (clubs.actions.ts, clubs.effects.ts, clubs.reducer.ts, clubs.selector.ts and clubs.state.ts).
├── app
│ ├── app.component.scss
│ ├── app.component.html
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── app.reducer.ts
│ ├── clubs
│ ├── clubs.component.ts
│ ├── clubs.module.ts
│ ├── clubs.service.ts
│ ├── index.ts
│ ├── components
│ │ └── table
│ │ ├── table.component.scss
│ | ├── table.component.html
│ | └── table.component.ts
│ └── store
│ ├── clubs.actions.ts
│ ├── clubs.effects.ts
│ ├── clubs.reducer.ts
│ ├── clubs.selector.ts
│ └── clubs.state.ts
├── assets
│ ├── dummy
│ │ └── clubs.json
│ └── img
│ ├── asc.png
│ └── desc.png
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── browserslist
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
├── tsconfig.app.json
├── tsconfig.spec.json
└── tslint.json
Setting store inside specific module
During creating store, it’s compulsory to create state. The main goal is to develop store in every module that is used. Also, it’s necessary to init module state name (clubs.state.ts) and afterwards import that in ClubsModule.
export const clubStateName = 'club-module';
In the view component there will be a table for displaying names of the clubs, short name, club code, name of stadium, capacity and coach. There will be a nice feature of sorting data per every column. Then, it’s recommended to init some enum that will have two types of sorting (ascending and descending). It will be possible to save name of column and type of sorting in the store.
export const clubStateName = 'club-module';
export const enum SortOrder {
ASCENDING = 'ASC',
DESCENDING = 'DESC'
}
export interface ClubsState {
id: number;
name: string;
short_name: string;
club_code: string;
stadium: string;
capacity: number;
manager: string;
}
export interface SortState {
field: string;
order: string;
}
export interface ClubModuleState {
clubs: ClubsState[];
sort: SortState;
}
export const initialClubModuleState = {
clubs: null,
sort: {
field: 'name',
order: SortOrder.ASCENDING
}
};
Actions
Actions are one of the main building blocks in NgRx. Actions express unique events that happen throughout your application. From user interaction with the page, external interaction through network requests, and direct interaction with device APIs, these and more events are described with actions.
There will be three types of action (request for clubs, respond for clubs and sorting). In action for response and sorting it is needed to define the type of payload. ActionType is used to define the type of action, so it can be referenced in effect and reducer.
import { Action } from '@ngrx/store';
import { ClubsState, SortState } from './clubs.state';
export const enum ClubsActionType {
REQUEST_CLUBS = '[CLUBS MODULE] REQUEST_CLUBS',
RESPOND_CLUBS = '[CLUBS MODULE] RESPOND_CLUBS',
SORT_CLUBS = '[CLUBS MODULE] SORT_CLUBS'
}
export class RequestClubsAction implements Action {
readonly type: ClubsActionType = ClubsActionType.REQUEST_CLUBS;
}
export class RespondClubsAction implements Action {
readonly type: ClubsActionType = ClubsActionType.RESPOND_CLUBS;
constructor(public payload: { clubs: ClubsState[] }) {}
}
export class SortClubsAction implements Action {
readonly type: ClubsActionType = ClubsActionType.SORT_CLUBS;
constructor(public payload: { sort: SortState }) {}
}
export type ClubAction =
| RequestClubsAction
| RespondClubsAction
| SortClubsAction;
Reducers
Reducers in NgRx are responsible for handling transitions from one state to the next state in your application. Reducer functions handle these transitions by determining which actions to handle based on the action’s type.
Reducer is triggered by dispatching an action that is going to be called. They connect everything in background anytime a new action is dispatched.
Within this application there are two actions that will update state. First action RESPOND_CLUBS get payload with the clubs from response and then update clubs in state. Second action SORT_CLUBS allows getting the type of sort (field and order).
Contact

Looking for agile software development experts?
First thing is to save new sort that is dispatched. After that, we need to copy array before sorting, because the array is frozen in strict mode.
Next step is to check type of sorting, then sorting array of clubs and at the end returning new state. At the end, it is mandatory to copy the old state if there are not changes in any other states. Finally, it’s necessary to set new changes in state. Also, it is needed to set default case in switch statement, where it is possible to return old state.
import {
ClubModuleState,
initialClubModuleState,
ClubsState,
SortState
} from './clubs.state';
import {
ClubAction,
ClubsActionType,
RespondClubsAction,
SortClubsAction
} from './clubs.actions';
export function clubReducer(
oldState: ClubModuleState = initialClubModuleState,
action: ClubAction
): ClubModuleState {
switch (action.type) {
case ClubsActionType.RESPOND_CLUBS: {
const clubs: ClubsState[] = (action as RespondClubsAction).payload.clubs;
const newState = {
...oldState,
clubs
};
return newState;
}
case ClubsActionType.SORT_CLUBS: {
const sort: SortState = (action as SortClubsAction).payload.sort;
const clubs: ClubsState[] = oldState.clubs.slice().sort((a, b) => {
const clubA =
sort.field !== 'capacity'
? a[sort.field].toUpperCase()
: a[sort.field];
const clubB =
sort.field !== 'capacity'
? b[sort.field].toUpperCase()
: b[sort.field];
if (sort.order === 'ASC') {
return clubA > clubB ? 1 : clubA < clubB ? -1 : 0;
} else {
return clubA < clubB ? 1 : clubA > clubB ? -1 : 0;
}
});
const newState = {
...oldState,
sort,
clubs
};
return newState;
}
default:
return oldState;
}
}
Effects
Effects are an RxJS powered side effect model for Store. Effects use streams to provide new sources of actions to reduce state based on external interactions such as network requests, web socket messages and time-based events.
Before starting with effects, in 90% of cases it is necessary to have REST service, that will return some response from backend (in our case we have dummy data in JSON file and it will return list of clubs).
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { ClubsState } from './store/clubs.state';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class ClubsService {
constructor(private http: HttpClient) {}
getClubs(): Observable<ClubsState[]> {
return this.http
.get<ClubsState[]>('./assets/dummy/clubs.json', {
observe: 'response'
})
.pipe(map(res => res.body['clubs']));
}
}
view raw
Effect is triggered when specific action is dispatched. It is similar to what reducers are doing, but in 90% of cases effects is used to calling REST service. After the effect is done, it returns new action that is going to dispatch and change/save data in store.
import { Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { Action } from '@ngrx/store';
import { Observable } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { ClubsService } from '../clubs.service';
import {
RequestClubsAction,
ClubsActionType,
RespondClubsAction
} from './clubs.actions';
import { ClubsState } from './clubs.state';
@Injectable()
export class ClubsEffects {
@Effect()
requestClubs$: Observable<Action> = this.actions$.pipe(
ofType<RequestClubsAction>(ClubsActionType.REQUEST_CLUBS),
switchMap(action => {
return this.clubService.getClubs().pipe(
map((response: ClubsState[]) => {
return new RespondClubsAction({ clubs: response });
})
);
})
);
constructor(
private readonly actions$: Actions,
private readonly clubService: ClubsService
) {}
}
Selector
Selectors are pure functions used for obtaining slices of store state. @ngrx/store provides a few helper functions for optimizing this selection. Selectors provide many features when selecting slices of state.
After all of the above is done (state, actions, reducer and effect), it’s obligatory to setup the selector. Selector is used to get specific data that is needed in specific view component. First step is to setup which state name is used. Second step is to export const with specific data that returns Observable with property type.
import { createFeatureSelector, Selector, createSelector } from '@ngrx/store';
import {
ClubModuleState,
clubStateName,
ClubsState,
SortState
} from './clubs.state';
const selectClubModule = createFeatureSelector<ClubModuleState>(clubStateName);
export const selectClub: Selector<object, ClubsState[]> = createSelector(
selectClubModule,
(state: ClubModuleState) => state.clubs
);
export const selectSortState: Selector<object, SortState> = createSelector(
selectClubModule,
(state: ClubModuleState) => state.sort
);
Smart and view component
Smart component is component where all actions are dispatched and where is possible to get all data from state using selectors. Input and output method is used to pass data or dispatch some function to/from view component. In OnInit method is allowed to dispatch actions that are needed. Also, it’s available to select properly selector for input method.
import { Component, OnInit } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { selectClub, selectSortState } from './store/clubs.selector';
import { RequestClubsAction, SortClubsAction } from './store/clubs.actions';
import { Observable } from 'rxjs';
import { ClubsState, SortState } from './store/clubs.state';
@Component({
selector: 'app-clubs',
template: `
<app-table
[clubs]="clubs$ | async"
[sortState]="sortState$ | async"
(doSort)="sort($event)"
></app-table>
`
})
export class ClubsComponent implements OnInit {
clubs$: Observable<ClubsState[]>;
sortState$: Observable<SortState>;
constructor(private store: Store<any>) {}
ngOnInit() {
this.store.dispatch(new RequestClubsAction());
this.clubs$ = this.store.pipe(select(selectClub));
this.sortState$ = this.store.pipe(select(selectSortState));
}
sort(event): void {
this.store.dispatch(new SortClubsAction({ sort: event }));
}
}
In the smart component, the async pipe is used for automatically subscribe to Observable and then return a new emit value. In case when changes happen in the state, async pipe automatically makes proper changes in component. Also, when the component is destroyed, async pipe automatically unsubscribes to avoid memory leak.
View component takes care for displaying the data and triggering event. It is not aware of how the data is retrieved or how the state changes. View components are based on the value of their input properties, nothing else. In view component folder const is initialised with the names and values for each column in table. Data is sent from smart component through input method. Output method for sorting is emitting new value that needs to be dispatched.
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { ClubsState, SortState, SortOrder } from '../../store/clubs.state';
import { TableSettings, tableSettings } from './table.settings';
@Component({
selector: 'app-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss']
})
export class TableComponent {
@Input() clubs: ClubsState[];
@Input() sortState: SortState;
@Output() doSort = new EventEmitter<SortState>();
tableSettings: TableSettings[] = tableSettings;
constructor() {}
sort(field: string): void {
const order: string =
field === this.sortState.field
? SortOrder.ASCENDING === this.sortState.order
? SortOrder.DESCENDING
: SortOrder.ASCENDING
: SortOrder.ASCENDING;
this.doSort.emit({ field, order });
}
}
<table class="table">
<thead>
<tr>
<th
scope="col"
*ngFor="let settings of tableSettings"
(click)="sort(settings.value)"
>
{{ settings.name }}
<img
src="./assets/img/{{
sortState.order === 'ASC' ? 'asc' : 'desc'
}}.png"
*ngIf="sortState.field === settings.value"
alt="{{ sortState.order }}"
/>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let club of clubs">
<td *ngFor="let settings of tableSettings">{{ club[settings.value] }}</td>
</tr>
</tbody>
</table>
export interface TableSettings {
name: string;
value: string;
}
export const tableSettings: TableSettings[] = [
{
name: 'Name',
value: 'name'
},
{
name: 'Short name',
value: 'short_name'
},
{
name: 'Club code',
value: 'club_code'
},
{
name: 'Stadium',
value: 'stadium'
},
{
name: 'Capacity',
value: 'capacity'
},
{
name: 'Manager',
value: 'manager'
}
];
view raw
Conclusions
In this article I tried to explain how and in which way we use NgRx in AG04. NgRx is very powerful and stable library that is very useful in big enterprise applications. This is an example how you can setup architecture for your application. However, if you’re going to work with this type of approach, it’ll take a lot more code to type.