Angular. State Management Problems. Redux. Understanding what is RxJS by comparing it to promises. Creating a simple app using ngrx/store and ngrx/effects. State Management Practices @ hillgrand.com.
4. ▸ State Management Problems.
▸ Observables vs Promises (Intro to RxJS).
▸ NGRX/Platform Overview.
▸ Creating a simple app using ngrx/store and ngrx/effects (v.8+).
▸ NGRX and State Management @ hillgrand.com
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
WHAT TO EXPECT
8. Component
Class View
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
MULTI COMPONENT - SERVICE COMMUNICATION
Class
Service (API)
Component
Class View
10. STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
WHERE SHOULD WE STORE STATE?
11. ▸ Only inside the components.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
WHERE SHOULD WE STORE STATE?
12. ▸ Only inside the components.
▸ Only inside the api services.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
WHERE SHOULD WE STORE STATE?
13. ▸ Only inside the components.
▸ Only inside the api services.
▸ In both the components and the api services.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
WHERE SHOULD WE STORE STATE?
15. ▸ The Single Responsibility Principle — Classes should have a single responsibility
and thus only a single reason to change.
▸ The Open/Closed Principle — Classes and other entities should be open for
extension but closed for modification.
▸ The Liskov Substitution Principle — Objects should be replaceable by their subtypes
▸ The Interface Segregation Principle — Interfaces should be client specific rather
than general.
▸ The Dependency Inversion Principle — Depend on abstractions rather than
concretions.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
SOLID PRINCIPLES OF OBJECT-ORIENTED DESIGN
16. ▸ The Single Responsibility Principle — Classes should have a single responsibility
and thus only a single reason to change.
▸ The Open/Closed Principle — Classes and other entities should be open for
extension but closed for modification.
▸ The Liskov Substitution Principle — Objects should be replaceable by their subtypes
▸ The Interface Segregation Principle — Interfaces should be client specific rather
than general.
▸ The Dependency Inversion Principle — Depend on abstractions rather than
concretions.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
SOLID PRINCIPLES OF OBJECT-ORIENTED DESIGN
17. ▸ Only inside the components.
▸ Only inside the api services.
▸ In both the components and the api services.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
WHERE SHOULD WE STORE STATE?
18. ▸ Only inside the components.
▸ Only inside the api services.
▸ In both the components and the api services.
▸ Inside one or more store/state services.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
WHERE SHOULD WE STORE STATE?
20. STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
REDUX PATTERN
Component Action Reducer
Store
21. STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
REDUX PATTERN WITH SELECTORS
Component Action Reducer
StoreSelector
22. ▸ Unified way of handling state management.
▸ Single source of truth.
▸ Predictable state transitions due to the pure reducer functions.
▸ Easier to debug
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
BENEFITS
24. ▸ Framework for building reactive applications in Angular.
▸ NgRx provides:
▸ State management
▸ Isolation of side effects
▸ Entity collection management
▸ Router bindings
▸ Developer tools that enhance developers experience
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
NGRX / PLATFORM
32. ▸ RxJS is a library for reactive programming using
Observables, to make it easier to compose
asynchronous or callback-based code.
RXJS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
33. ▸ RxJS is a library for reactive programming using
Observables, to make it easier to compose
asynchronous or callback-based code.
RXJS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
36. Promise.resolve(10).then(x => x + 100).then(x => '' + x); // > Promise { value: '110' }
PROMISE CHAIN
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
37. Promise.resolve(10).then(x => x + 100).then(x => '' + x); // > Promise { value: '110' }
PROMISE CHAIN
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
38. Promise.resolve(10).then(x => x + 100).then(x => '' + x); // > Promise { value: '110' }
PROMISE CHAIN
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
39. Promise.resolve(10).then(x => x + 100).then(x => '' + x); // > Promise { value: '110' }
PROMISE CHAIN
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
40. ▸ A state machine with three states - Unfulfilled / Resolved / Rejected.
▸ A Promise is a proxy for a value not necessarily known when the promise
is created.
▸ It allows you to associate handlers with an asynchronous action's
eventual success value or failure reason. (using .then / .catch)
▸ This lets asynchronous methods return values like synchronous methods:
instead of immediately returning the final value, the asynchronous
method returns a promise to supply the value at some point in the future.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
PROMISES
52. import { of } from 'rxjs';
import { map } from 'rxjs/operators';
of(10).pipe(map(x => x + 100), map(x => '' + x)).subscribe(console.log);
OBSERVABLES WITH PIPEABLE OPERATORS (1)
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
53. import { from } from 'rxjs';
import { map } from 'rxjs/operators';
const stream$ = from([1, 2, 3, 4]).pipe(map(x => x + 1));
stream$.subscribe(console.log)
// > 2
// > 3
// > 4
// > 5
MULTI VALUE OBSERVABLE (1)
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
54. import { from } from 'rxjs';
import { map } from 'rxjs/operators';
const stream$ = from([1, 2, 3, 4]).pipe(map(x => x + 1));
stream$.subscribe(console.log)
// > 2
// > 3
// > 4
// > 5
MULTI VALUE OBSERVABLE (3)
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
55. import { from } from 'rxjs';
import { map } from 'rxjs/operators';
const stream$ = from([1, 2, 3, 4]).pipe(map(x => x + 1));
stream$.subscribe(console.log)
// > 2
// > 3
// > 4
// > 5
MULTI VALUE OBSERVABLE (2)
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
136. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { Actions } from '@ngrx/effects';
@Injectable()
export class UserListEffects {
constructor(private actions$: Actions ) { }
}
137. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { Actions } from '@ngrx/effects';
@Injectable()
export class UserListEffects {
loadUsers$ =
constructor(private actions$: Actions ) { }
}
138. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { createEffect, Actions } from '@ngrx/effects';
@Injectable()
export class UserListEffects {
loadUsers$ = createEffect(
);
constructor(private actions$: Actions ) { }
}
139. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { createEffect, Actions } from '@ngrx/effects';
@Injectable()
export class UserListEffects {
loadUsers$ = createEffect(() => this.actions$.pipe(
);
constructor(private actions$: Actions ) { }
}
140. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { loadUsers } from '../actions/list';
@Injectable()
export class UserListEffects {
loadUsers$ = createEffect(() => this.actions$.pipe(
ofType(loadUsers),
);
constructor(private actions$: Actions ) { }
}
141. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { loadUsers } from '../actions/list';
import { switchMap } from 'rxjs/operators';
@Injectable()
export class UserListEffects {
loadUsers$ = createEffect(() => this.actions$.pipe(
ofType(loadUsers),
switchMap(
));
constructor(private actions$: Actions ) { }
}
142. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { loadUsers } from '../actions/list';
import { switchMap } from 'rxjs/operators';
@Injectable()
export class UserListEffects {
loadUsers$ = createEffect(() => this.actions$.pipe(
ofType(loadUsers),
switchMap(() =>
));
constructor(private actions$: Actions ) { }
}
We will be returning an observable so
we need to flatten it to get the value
143. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { loadUsers } from '../actions/list';
import { UserService } from '../../user.service';
import { switchMap } from 'rxjs/operators';
@Injectable()
export class UserListEffects {
loadUsers$ = createEffect(() => this.actions$.pipe(
ofType(loadUsers),
switchMap(() => this.userService.loadUsers()
));
constructor(private actions$: Actions, private userService: UserService) { }
}
144. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { loadUsers } from '../actions/list';
import { UserService } from '../../user.service';
import { switchMap } from 'rxjs/operators';
@Injectable()
export class UserListEffects {
loadUsers$ = createEffect(() => this.actions$.pipe(
ofType(loadUsers),
switchMap(() => this.userService.loadUsers().pipe(
)
));
constructor(private actions$: Actions, private userService: UserService) { }
}
145. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { loadUsersSuccess, loadUsers } from '../actions/list';
import { UserService } from '../../user.service';
import { switchMap, map } from 'rxjs/operators';
@Injectable()
export class UserListEffects {
loadUsers$ = createEffect(() => this.actions$.pipe(
ofType(loadUsers),
switchMap(() => this.userService.loadUsers().pipe(
map( ),
)
));
constructor(private actions$: Actions, private userService: UserService) { }
}
146. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { loadUsersSuccess, loadUsers } from '../actions/list';
import { UserService } from '../../user.service';
import { switchMap, map } from 'rxjs/operators';
@Injectable()
export class UserListEffects {
loadUsers$ = createEffect(() => this.actions$.pipe(
ofType(loadUsers),
switchMap(() => this.userService.loadUsers().pipe(
map(users => loadUsersSuccess(users)),
)
));
constructor(private actions$: Actions, private userService: UserService) { }
}
147. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { loadUsersSuccess, loadUsers } from '../actions/list';
import { UserService } from '../../user.service';
import { switchMap, map, catchError } from 'rxjs/operators';
@Injectable()
export class UserListEffects {
loadUsers$ = createEffect(() => this.actions$.pipe(
ofType(loadUsers),
switchMap(() => this.userService.loadUsers().pipe(
map(users => loadUsersSuccess(users)),
catchError( ))
)
));
constructor(private actions$: Actions, private userService: UserService) { }
}
148. CREATING OUR FEATURE EFFECTS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
import { Injectable } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { loadUsersSuccess, loadUsersFailure, loadUsers } from '../actions/list';
import { UserService } from '../../user.service';
import { switchMap, map, catchError } from 'rxjs/operators';
@Injectable()
export class UserListEffects {
loadUsers$ = createEffect(() => this.actions$.pipe(
ofType(loadUsers),
switchMap(() => this.userService.loadUsers().pipe(
map(users => loadUsersSuccess(users)),
catchError(error => [loadUsersFailure(error)]))
)
));
constructor(private actions$: Actions, private userService: UserService) { }
}
153. ▸ Listens for one or multiple actions and returns an action.
▸ Listens for one or multiple actions and returns nothing.
▸ Listens for one or multiple actions and returns multiple actions.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
TYPES OF EFFECT STREAMS
154. ▸ @ngrx/store - state management.
▸ @ngrx/store-devtools - developer tools and instrumentation.
▸ @ngrx/effects - side effect isolation.
▸ @ngrx/router-store - Angular router bindings.
▸ @ngrx/entity - adapter for managing record collections.
▸ @ngrx/data - extension that simplifies management of entity data.
▸ @ngrx/schematics - scaffolding module for ngrx.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
NGRX / PLATFORM
155. ▸ @ngrx/store - state management.
▸ @ngrx/store-devtools - developer tools and instrumentation.
▸ @ngrx/effects - side effect isolation.
▸ @ngrx/router-store - Angular router bindings.
▸ @ngrx/entity - adapter for managing record collections.
▸ @ngrx/data - extension that simplifies management of entity data.
▸ @ngrx/schematics - scaffolding module for ngrx.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
NGRX / PLATFORM
156. ▸ @ngrx/store - state management.
▸ @ngrx/store-devtools - developer tools and instrumentation.
▸ @ngrx/effects - side effect isolation.
▸ @ngrx/router-store - Angular router bindings.
▸ @ngrx/entity - adapter for managing record collections.
▸ @ngrx/data - extension that simplifies management of entity data.
▸ @ngrx/schematics - scaffolding module for ngrx.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
NGRX / PLATFORM
157. ▸ @ngrx/store - state management.
▸ @ngrx/store-devtools - developer tools and instrumentation.
▸ @ngrx/effects - side effect isolation.
▸ @ngrx/router-store - Angular router bindings.
▸ @ngrx/entity - adapter for managing record collections.
▸ @ngrx/data - extension that simplifies management of entity data.
▸ @ngrx/schematics - scaffolding module for ngrx.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
NGRX / PLATFORM
158. ▸ @ngrx/store - state management.
▸ @ngrx/store-devtools - developer tools and instrumentation.
▸ @ngrx/effects - side effect isolation.
▸ @ngrx/router-store - Angular router bindings.
▸ @ngrx/entity - adapter for managing record collections.
▸ @ngrx/data - extension that simplifies management of entity data.
▸ @ngrx/schematics - scaffolding module for ngrx.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
NGRX / PLATFORM
159. ▸ @ngrx/store - state management.
▸ @ngrx/store-devtools - developer tools and instrumentation.
▸ @ngrx/effects - side effect isolation.
▸ @ngrx/router-store - Angular router bindings.
▸ @ngrx/entity - adapter for managing record collections.
▸ @ngrx/data - extension that simplifies management of entity data.
▸ @ngrx/schematics - scaffolding module for ngrx.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
NGRX / PLATFORM
160. ▸ @ngrx/store - state management.
▸ @ngrx/store-devtools - developer tools and instrumentation.
▸ @ngrx/effects - side effect isolation.
▸ @ngrx/router-store - Angular router bindings.
▸ @ngrx/entity - adapter for managing record collections.
▸ @ngrx/data - extension that simplifies management of entity data.
▸ @ngrx/schematics - scaffolding module for ngrx.
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
NGRX / PLATFORM
171. META REDUCERS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
We can think of meta-reducers as hooks into the action->reducer pipeline.
Meta-reducers allow us to pre-process actions before normal reducers are
invoked.
export function logoutStateCleaner(reducer) {
return function (state: State, action: Action) {
return reducer(action.type === logoutSuccess.type ? {
router: state.router,
// more things that you want to persist after logout
} : state, action);
};
}
173. REDUCING STATE BY USING QUERY PARAMS
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
https://github.com/IliaIdakiev/
query-param-store
174. GitHub > https://github.com/iliaidakiev (/slides/ - list of future and past events)
Twitter > @ilia_idakiev
CONNECT
STATE MANAGEMENT FOR ENTERPRISE ANGULAR APPLICATIONS
Colour palette and background image + ngrx architecture image were taken from https://ngrx.io