Understanding NGRX


Andrés Gesteira
Twitter: Andres Gesteira (@GeorgeKaplan_G)
Former TV Assistant Director
Loves 70s and 80s films
Angular Developer
Loves playing piano
And of course... loves Angular

NGRX
What is ngrx?
ng stands for Angular.
rx stands for Reactive.
It is inspired by Redux.
So it is a package of reactive statement management libraries...
...and made for Angular.
What libraries?
The essential ones:
The optional ones:
The future:
What is the Redux architecture?
It is a sowftware architecture paradigm.
It embraces functional (akas reactive) programming.
It comes down to 3 principles:
Stores a gigantic JS object literal called State.
- State is the single source of truth.
- State is immutable.
- One-way data flow.
What belongs to the State: SHARI
SHARED: state that is accessed by many components and services.
HYDRATED: state that is persisted and hydrated from storage.
RETRIEVED: state that needs to be retrieved with a side effect.
AVAILABLE: state that needs to be available when re-entering routes.
IMPACTED: state that is impacted by actions from other sources.
What does not belong to the State
Forms. Angular's reactive forms module is already its own state manager.
Non-serializable data.
Data that is solely owned by a component.
What is ngrx good for?
Predictability.
Performance.
Testing
Debugging
Immutability and
Entity Pattern
What is immutable in JS?
Arrays...
Objects...
Numbers...
Strings...
are mutable!
are mutable!
are immutable!
are immutable!
Array mutable/immutable ops
/* Mutable */
const names = ['Captain America', 'Iron Man'];
names.push('Thor');
console.log(names); // ['Captain America', 'Iron Man', 'Thor']
/* Immutable */
const names = ['Captain America', 'Iron Man'];
const newNames = [...names, 'Thor'];
console.log(names); // ['Captain America', 'Iron Man']
console.log(newNames): // ['Captain America', 'Iron Man', 'Thor']
Object mutable/immutable ops
/* Mutable */
const character = { publicId: 'Peter Parker' };
character.secretId = 'Spider-Man';
console.log(character); // { publicId: 'Peter Parker', secretId: 'Spider-Man' }
/* Immutable */
const character = { publicId: 'Peter Parker' };
const updatedCharacter = { ...character, secretId: 'Spider-Man' };
// OR const updatedCharacter = Object.assign({}, character, { secretId: 'Spider-Man' });
console.log(character); // { publicId: 'Peter Parker' }
console.log(updatedCharacter); // { publicId: 'Peter Parker', secretId: 'Spider-Man' }
Entity Pattern (I)
/* state.model.ts */
export interface LoadableState {
loadableEntities: Entities;
loaded: boolean;
loading: boolean;
}
export interface Entities {
ids: number[];
entities: {
[entityId: number]: EntityType;
};
}
export interface Entity {
id: number;
}
export interface CourseEntity extends Entity {
id: number;
subject: string;
}
export interface LessonEntity extends Entity {
id: number;
topic: string;
}
export type EntityType = CourseEntity | LessonEntity;
Entity Pattern (II)
/* it('should produce this kind of object literal') */
{
loadableEntities: {
ids: [1, 5, 3],
entities: {
[1]: {
id: 1,
subject: 'Javascript'
},
[3]: {
id: 3,
subject: 'Typescript'
},
[5]: {
id: 5,
subject: 'Angular'
}
}
},
loaded: true,
loading: false
}
Core Concepts
Store
The place where the State is composed.
It receives our app actions.
It mostly allocates cache memory with non-persistent data.
It invokes the reducers.
Tip: check the memory of your Chrome: chrome://net-internals/#httpCache
Actions
Everything starts with an action:
Actions represent a unique event from our application.
They can ONLY have 2 properties:
They are "dispatched" from a component and sent to the reducer.
They should never be re-used for the same thing.
The action type should be descriptive enough. Clarity over brevity.
- From the user.
- From the Back End.
- From the device.
- type: string
- payload: any (optional)
Actions
import { Action } from '@ngrx/store';
import * as fromModels from '../../models';
export enum ItemsActionTypes {
LoadItemAction = '[Feature] Load Item',
LoadItemSuccessAction = '[Feature] Load Item Success',
LoadItemFailAction = '[Feature] Load Item Fail'
}
export class LoadItem implements Action {
readonly type = ItemsActionTypes.LoadItemAction;
constructor(public payload: { itemId: number }) {}
}
export class LoadItemSuccess implements Action {
readonly type = ItemsActionTypes.LoadItemSuccessAction;
constructor(public payload: { item: fromModels.Item; }) {}
}
export class LoadItemFail implements Action {
readonly type = ItemsActionTypes.LoadItemFailAction;
constructor(public payload: { error: any; itemId: number }) {}
}
export type ItemsAction = LoadItem | LoadItemSuccess | LoadItemFail;
Reducers
They are pure functions that update the State tree.
They compose a new state and return it.
This operation is always performed by a switch statement.
- do not modify global variables.
- do not produce side effects (like HTTP calls).
Because they are pure they:
Reducers
export function reducer(state = initialState: ItemsLoadableState, action: ItemsAction): ItemsLoadableState {
switch (action.type) {
case ItemsActionTypes.LoadItemAction: {
return {
loadableItemEntities: state.loadableItemEntities,
loading: true,
loaded: false
};
}
case ItemsActionTypes.LoadItemSuccessAction: {
const item = action.payload.item;
const itemId = action.payload.item.itemId;
const ids = [...state.loadableItemEntities.ids, itemId];
return {
loadableItemEntities: {
ids,
entities: {
...state.loadableItemEntities.entities,
[itemId]: item
}
},
loading: false,
loaded: true
};
}
case ItemsActionTypes.LoadItemFailAction: {
return {
loadableItemEntities: state.loadableItemEntities,
loading: false,
loaded: false
};
}
}
return state;
}
Effects
They isolate side effects from components.
They listen to actions and automatically dispatch (or not dispatch) other actions.
They communicate outside Angular.
Effects
import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { of } from 'rxjs/observable/of';
import { catchError, map, switchMap } from 'rxjs/operators';
import * as fromServices from '../../services';
import * as itemsActions from '../actions/items.action';
@Injectable()
export class ItemsEffects {
@Effect()
loadItem$ = this.actions$.ofType(itemsActions.ItemsActionTypes.LoadItemAction).pipe(
map((action: itemsActions.LoadItem) => action.payload.itemId),
switchMap(itemId => {
return this.myService
.getItem(itemId)
.pipe(
map(item => new itemsActions.LoadItemSuccess({ item })),
catchError(error => of(new itemsActions.LoadItemFail({ error, itemId })))
);
})
);
constructor(private actions$: Actions, private myService: fromServices.MyService) {}
}
Selectors
They are just queries to your store.
They can help to keep reducers simple.
They return an observable that you can use in the component.
Selectors
import { createSelector } from '@ngrx/store';
import * as fromConstants from '../../../app/constants';
import * as fromRoot from '../../../app/store';
import * as fromFeature from '../reducers';
import * as fromItems from '../reducers/items.reducer';
export const getItemsState = createSelector(
fromFeature.getFeatureState,
(state: fromFeature.getFeatureState) => state.items
);
export const getSelectedItemState = createSelector(
getItemsState,
fromRoot.getRouterReducerPrimaryState,
(items, router): fromItems.ItemState => {
return (
router.state &&
Object.keys(items).length !== 0 &&
items[router.state.params[fromConstants.params.itemId]]
);
}
);
Reactive Angular
Container Components
They are aware of the Store.
They dispatch actions and select features from the Store.
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './my-container.component.html'
})
export class MyContainerComponent implements OnInit {
entities$: Observable<any[]>;
constructor(private store: Store<fromStore.FeatureState>) {}
ngOnInit() {
this.entities$ = this.store.select(fromStore.getSelectedItemEntities);
}
dispatchAddsItem(e: fromModels.Item) {
this.store.dispatch(new fromStore.AddsItem(e));
}
}
Presentational Components
They are NOT aware of the Store.
They invoke callbacks via @Output.
<presentational-comp
[entities]="entities$ | async"
(emittedOutput)="dispatchAddsItem($event)">
</presentational-comp>
And read data via @Input.
/* In a presentational component class */
@Output() emittedOutput = new EventEmitter<any>();
@Input() entities: any;
emittingMethod(e) {
this.emittedOutput.emit(e);
}
*Reactive Angular Data Flow


*This diagram was designed by Todd Motto.
Demo
Conclusions
"ngrx is not great for making simple things quickly.
It is great for making realy hard things simple".
When to use ngrx
To control app-wide states such as Authentication.
When we need to track the State of our application.
For most scalable applications.
When sibling components need to share information.
For Angular Universal apps.
When not to use ngrx
When your states consists of booleans.
When you do not have enough experience with Angular.
When your app is small and it is going to remain small.
When there is not going to be shared information in your app.
Workaround (I)
/* emiter.service.ts */
@Injectable()
export class EmitterService {
private emitters: { [id: string]: EventEmitter<any> } = {};
static get(id: string): EventEmitter<any> {
if (!this.emitters[id])
this.emitters[id] = new EventEmitter();
return this.emitters[id];
}
}
/* dispatcher.component.ts */
@Component()
class DispatcherComponent {
constructor(private emitterService: EmitterService) {}
dispatchValue(state: string, val: any) {
this.emitterService.get(this.id).emit(val);
}
}
/* selector.component.ts */
@Component()
class SelectorComponent implements OnInit {
constructor(private emitterService: EmitterService) {}
ngOnInit() {
this.emitterService.get('OnInit state').subscribe(value => console.log(value));
}
}
Workaround (II)
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subject } from 'rxjs/Subject';
import { takeUntil } from 'rxjs/operators';
import { MarvelService } from '../../marvel.service';
@Component()
export class ComicsComponent implements OnInit, OnDestroy {
comics$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
private destroy$: Subject<boolean> = new Subject<boolean>();
constructor(private marvelService: MarvelService) {}
ngOnInit() {
this.marvelService
.getComics('Doctor Strange')
.pipe(takeUntil(this.destroy$))
.subscribe(comics => {
this.comics$.next(comics);
});
}
public ngOnDestroy() {
this.destroy$.next();
}
}
You use it to fight the boilerplate.
It is based on Angular Schematics:
https://blog.angular.io/schematics-an-introduction-dc1dfbc2a2b2
It can generate templates for:
- Action
- Container
- Effect
- Entity
- Feature
- Reducer
- Store
Tip: do not try to extend @ngrx/schematics to create custom ones.
Tip: come to this meetup if you want to learn more about the topic.
The End
Understanding NGRX
By Andres Gesteira
Understanding NGRX
- 1,415