NGRX Apps in Depth
-
Upload
trayan-iliev -
Category
Software
-
view
350 -
download
1
Transcript of NGRX Apps in Depth
June 15, 2017IPT – Intellectual
Products & Technologies
NGRX Apps in Depth – RxJS,
Reselect, Router, IndexedDB,
@Effects
Trayan [email protected]://iproduct.org
Copyright © 2003-2017 IPT - Intellectual Products & Technologies
IPT - Intellectual Products & Technologies
2
Since 2003 we provide trainings and share skills in JS/ TypeScript/ Node/ Express/ Socket.IO/ NoSQL/ Angular/ React / Java SE/ EE/ Web/ REST SOA:
Node.js + Express/ hapi + React.js + Redux + GraphQL
Angular + TypeScript + Redux (ngrx)
Java EE6/7, Spring, JSF, Portals: Liferay, GateIn
Reactive IoT with Reactor / RxJava / RxJS
SOA & Distributed Hypermedia APIs (REST)
Domain Driven Design & Reactive Microservices
3
NGRX Apps in Depth
State management, event sourcing, DDD, reactive programming, and stream based service architectures
Flux, Redux & NGRX: Reactive Extensions for Angular
Composing NGRX Reducers, Selectors & Middleware
Computing derived data (memoization): Reselect, RxJS
Observable (hot) streams of async actions – isolating side effects using @Effect & RxJS reactive transforms
NGRX-Router integration, Material Design, PrimeNG
Normalization/denormalization, local data – IndexedDB
Example app – code structure, lazy loading, etc.
Where to Find the Demo Code?
4
Angular and NGRX demos are available @GitHub:
https://github.com/iproduct/course-angular
ipt-knowledge-manager – NGRX, Reselect, RxJS @Effects, modules lazy loading, AOT
angular2-change-detection-demos – modified from https://github.com/thoughtram/angular2-change-detection-demos
ngrx-example-app – NGRX official demo from https://github.com/ngrx/example-app, IndexedDB
Data / Event / Message Streams
5
“Conceptually, a stream is a (potentially never-ending) flow of data records, and a transformation is an operation that takes one or more streams as input, and produces one or more output streams as a result.”
Apache Flink: Dataflow Programming Model
Data Stream Programming
6
The idea of abstracting logic from execution is hardly new -- it was the dream of SOA. And the recent emergence of microservices and containers shows that the dream still lives on.
For developers, the question is whether they want to learn yet one more layer of abstraction to their coding.
… there's the elusive promise of a common API to streaming engines that in theory should let you mix and match, or swap in and swap out.
Tony Baer (Ovum) @ ZDNet - Apache Beam and Spark: New comopetition for squashing the Lambda Architecture?
Listen to Quark:
7
“Good things come in small packages”
Quark – Star Trek DS9 character
https://en.wikipedia.org/w/index.php?curid=12544179, Star Trek: Deep Space Nine, "Emissary", Fair use
Lambda Architecture - I
8
https://commons.wikimedia.org/w/index.php?curid=34963986, By Textractor - Own work, CC BY-SA 4
Lambda Architecture - II
9
https://commons.wikimedia.org/w/index.php?curid=34963987, By Textractor - Own work, CC BY-SA 4
Lambda Architecture - III
10
Data-processing architecture designed to handle massive quantities of data by using both batch- and stream-processing methods
Balances latency, throughput, fault-tolerance, big data, real-time analytics, mitigates the latencies of map-reduce
Data model with an append-only, immutable data source that serves as a system of record
Ingesting and processing timestamped events that are appended to existing events. State is determined from the natural time-based ordering of the data.
Druid Distributed Data Store
11
https://commons.wikimedia.org/w/index.php?curid=33899448 By Fangjin Yang - sent to me personally, GFDL
Apache ZooKeeper
MySQL / PostgreSQL
HDFS / Amazon S3
Lambda Architecture: Projects - I
12
Direct Acyclic Graphs - DAG
13
14
Performance is about 2 things:– Throughput – units per second, and – Latency – response time, responsiveness - especially
important from end user perspective (front-end)
Real-time – time constraint from input to response regardless of system load.
Hard real-time system if this constraint is not honored then a total system failure can occur.
Soft real-time system – low latency response with little deviation in response time
100 nano-seconds to 100 milli-seconds. [Peter Lawrey]
What High Performance Means?
Synchronous vs. Asynchronous IO
15
DB
Synchronous
A
A
B
B
DB
Asynchronous
A
B
C
D
A
B
C
D
Tracking Complexity
16
We need tools to cope with all that complexity inherent in real world applications.
Simple solutions are needed – cope with problems through divide and concur on different levels of abstraction:
Domain Driven Design (DDD) – back to basics: domain objects, data and logic.
Described by Eric Evans in his book: Domain Driven Design: Tackling Complexity in the Heart of Software, 2004
Microservices and DDD
17
Main concepts:
Entities, value objects and modules
Aggregates and Aggregate Roots:
value < entity < aggregate < module < BC
Aggregate Roots are exposed as Open Host Services
Hexagonal architecture :
OUTSIDE <-> transformer <-> ( application <-> domain )
Imperative and Reactive
18
We live in a Connected Universe
... there is hypothesis that all the things in the Universe are intimately connected, and you can not change a bit without changing all.
Action – Reaction principle is the essence of how Universe behaves.
Imperative and Reactive
Reactive Programming: using static or dynamic data flows and propagation of change
Example: a := b + c
Functional Programming: evaluation of mathematical functions, ➢ Avoids changing-state and mutable data, declarative
programming➢ Side effects free => much easier to understand and
predict the program behavior. Example: Observable.from(['Reactive', 'Extensions','JS'])
.take(2).map(s => `${s}: on ${new Date()}`) .subscribe(s => console.log(s));
Functional Reactive (FRP)
20
According to Connal Elliot's (ground-breaking paper @Conference on Functional Programming, 1997), FRP is: (a) Denotative (Compositional): Observables can be
composed with higher-order combinators (b) Temporally continuous (Lazy): Observables do
not start emitting data until an Observer has subscribed
22
Message Driven – asynchronous message-passing allows to establish a boundary between components that ensures loose coupling, isolation, location transparency, and provides the means to delegate errors as messages.
[Reactive Manifesto]
Scalable, Massively Concurrent
Reactive Programming Specs
23
Reactive Streams Specification [http://www.reactive-streams.org/]
ES7 Observable Spec (implemented by RxJS 5) [https://github.com/tc39/proposal-observable]
Open source polyglot project ReactiveX (Reactive Extensions) [http://reactivex.io]:
Rx = Observables + LINQ + Schedulers :)Java: RxJava, JavaScript: RxJS, C#: Rx.NET, Scala: RxScala, Clojure: RxClojure, C++: RxCpp, Ruby: Rx.rb, Python: RxPY, Groovy: RxGroovy, JRuby: RxJRuby, Kotlin: RxKotlin ...
Reactive Streams Spec.
24
Publisher – provider of potentially unbounded number of sequenced elements, according to Subscriber(s) demand.
Publisher.subscribe(Subscriber) => onSubscribe onNext* (onError | onComplete)?
Subscriber – calls Subscription.request(long) to receive notifications
Subscription – one-to-one Subscriber ↔ Publisher, request data and cancel demand (allow cleanup).
Processor = Subscriber + Publisher
ES7 Observable Spec (RxJS 5)
25
interface Observable { constructor(subscriber : SubscriberFunction);
subscribe(observer : Observer) : Subscription;
subscribe(onNext : Function, onError? : Function, onComplete? : Function) : Subscription;
[Symbol.observable]() : Observable;
static of(...items) : Observable;
static from(iterableOrObservable) : Observable;}
interface Subscription { unsubscribe() : void; get closed() : Boolean;}
26
RxJS – JS ReactiveX (Reactive Extensions)[http://reactivex.io, https://github.com/ReactiveX]
ReactiveX is a polyglot library for composing asynchronous event streams (observable sequences).
It extends the observer pattern by declarative composition of functional transformations on events streams (e.g. map-filter-reduce, etc.)
Abstracs away low-level concerns like concurrency, synchronization, and non-blocking I/O.
Follows the next - error - completed event flow
Allows natural implementation of Redux design pattern
Alternative (together with promises) for solving “callback hell” problem
27
RxJS Resources
RxMarbles:
http://rxmarbles.com/
RxJS Coans:https://github.com/Reactive-Extensions/RxJSKoans
Learn RxJS:https://www.learnrxjs.io/
Source: https://github.com/ReactiveX/rxjs/blob/master/doc/asset/marble-diagram-anatomy.svg, Apache v2
Anatomy of Rx Operator
Example: combineLatest()
29
https://projectreactor.io/core/docs/api/, Apache Software License 2.0
Example: switchMap()
30
http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html, Apache Software License 2.0
Hot and Cold Event Streams
31
PULL-based (Cold Event Streams) – Cold streams are streams that run their sequence when and if they are subscribed to. They present the sequence from the start to each subscriber.
PUSH-based (Hot Event Streams) – Hot streams emit values independent of individual subscriptions. They have their own timeline and events occur whether someone is listening or not. An example of this is mouse events. A mouse is generating events regardless of whether there is a subscription. When subscription is made observer receives current events as they happen.
Converting Cold to Hot Stream
32
Flux Design Pattern
Source: Flux in GitHub, https://github.com/facebook/flux, License: BSD 3-clause "New" License
Linear flow:Source: @ngrx/store in GitHub, https://gist.github.com/btroncone/a6e4347326749f938510
Redux Design Pattern
Source: RxJava 2 API documentation, http://reactivex.io/RxJava/2.x/javadoc/
Redux == Rx Scan Opearator
Source: RxJS docs, https://github.com/ReactiveX/rxjs/blob/master/doc/tutorial/applications.md
Redux in Plain RxJS
import Immutable from 'immutable';import someObservable from './someObservable';import someOtherObservable from './someOtherObservable';
var initialState = { foo: 'bar' };
var state = Observable.merge( someObservable, someOtherObservable).scan((state, changeFn) => changeFn(state), Immutable.fromJS(initialState));
export default state;
Source: RxJS docs, https://github.com/ReactiveX/rxjs/blob/master/doc/tutorial/applications.md
Redux in Plain RxJS
state.js:
import Immutable from 'immutable';import someObservable from './someObservable';import someOtherObservable from './someOtherObservable';
var initialState = { foo: 'bar' };
var state = Observable.merge( someObservable, someOtherObservable).scan((state, changeFn) => changeFn(state), Immutable.fromJS(initialState));
export default state;
Redux in Plain RxJS
client.js:
import state from './state';
state.subscribe(state => { document.querySelector('#text').innerHTML = state.get('foo');});
Source: RxJS docs, https://github.com/ReactiveX/rxjs/blob/master/doc/tutorial/applications.md
NGRX: Reactive Extensions for Angular
core – core functionality for the ngrx platform
store – RxJS powered state management for Angular applications, inspired by Redux
router-store – bindings to connect the Angular Router to @ngrx/store
effects – side effect model for @ngrx/store
db – RxJS powered IndexedDB for Angular apps
notify – Web Notifications powered by RxJS for Angular
store-devtools, store-log-monitor - dev tools, monitoring
example-app – example app showcasing ngrx platform
Bootstraping NGRX App
In app.module.ts: @NgModule({ imports: [
StoreModule.provideStore(reducer),RouterStoreModule.connectRouter(),
StoreDevtoolsModule.instrumentOnlyWithExtension(), EffectsModule.run(BookEffects),
EffectsModule.run(CollectionEffects),DBModule.provideDB(schema),
... ] ...
NGRX: Defining State & Reducers
in root.reducer.ts:
export interface RootState { router: fromRouter.RouterState; ui: fromUi.State; users: fromUsers.State; tests: fromTests.State;}
export const reducers: ReducersMap = { router: fromRouter.routerReducer, ui: fromUi.reducer, users: fromUsers.reducer, tests: fromTests.reducer};
NGRX: Composing Root Reducer in root.reducer.ts:
const developmentReducer: ActionReducer<S> = compose(storeFreeze, combineReducers)(reducers);const productionReducer: ActionReducer<S> = combineReducers(reducers);
export function rootReducer(state: any, action: any) { if (environment.production) { return productionReducer(state, action) } else { return developmentReducer(state,action) }}
Store Middleware Composition
Needs to be statically importable by AOT
NGRX Selectors
A selector function is a factory for mapping functions.
Returned function maps from larger state tree into a feature substate tree (destructing the larger state).
Selectors are used with the `select` ngrx Store operator. Following example shows selector selecting the `users` sub-state:
class ClientComponent { constructor(store$: Observable<State>) {
this.usersState$ = store$.select(getUsersState); } }
Composing User Selectors in users/user.selectors.ts: // User state selectorsexport const getEntities = (state: State) => state.entities;export const getIds = (state: State) => state.ids;export const getAll = createSelector(getEntities, getIds, (entities, ids)=>{ return ids.map(id => entities[id]); });
// Root state selectorsexport const getUsersState = (state: RootState) => state.users;export const getUsers = createSelector(getUsersState, getAll);
Using Reselect in users/user.selectors.ts: // User state selectorsexport const getEntities = (state: State) => state.entities;export const getIds = (state: State) => state.ids;export const getAll = createSelector(getEntities, getIds, (entities, ids)=>{ return ids.map(id => entities[id]); });
// Root state selectorsexport const getUsersState = (state: RootState) => state.users;export const getUsers = createSelector(getUsersState, getAll);
Computing Derived Data: Reselect
Selectors can compute derived data, allowing Redux to store the minimal possible state.
Selectors are efficient. A selector is not recomputed unless one of its arguments change.
Selectors are composable. They can be used as input to other selectors.
Works correctly only when combined with immutability.
Computing Derived Data: Reselectimport { createSelector } from 'reselect'const getVisibilityFilter = (state) => state.visibilityFilterconst getTodos = (state) => state.todosexport const getVisibleTodos = createSelector( [ getVisibilityFilter, getTodos ], (visibilityFilter, todos) => { switch (visibilityFilter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.completed) case 'SHOW_ACTIVE': return todos.filter(t => !t.completed)}});
Can You Spot the Problem Here?const isFirstTodoCompleteSelector = createSelector(state => state.todos[0], todo => todo && todo.completed)
export default function todos(state = initState, action) { switch (action.type) { case COMPLETE_ALL: const allMarked = state.every(todo => todo.completed) return state.map(todo => { todo.completed = !allMarked return todo }) default: return state}}
Can We Memoize without Reselect?
In users/components/user-list.component.ts:
public ngOnInit() { this.store.dispatch(this.userActions.loadUsers()); this.subscription = this.selectedId$ .filter(id => !!id) .distinctUntilChanged() .subscribe(id => this.store.dispatch( go(['users', id])));
Router Integration, MD, PrimeNG
State Normalization / Denormalization [http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html]
When a piece of data is duplicated in several places, it becomes harder to make sure that it is updated appropriately
Nested data means that the corresponding reducer logic has to be more nested or more complex. In particular, trying to update a deeply nested field can become very ugly very fast.
Since immutable data updates require all ancestors in the state tree to be copied and updated as well, an update to a deeply nested data object could force totally unrelated UI components to re-render even if the data they're displaying hasn't actually changed.
Example: Users State Normalization
export interface State { ids: IdentityType[]; entities: { [id: string]: User }; selectedUserId: IdentityType | null; loading: boolean;};export const initialState: State = { ids: [], entities: {}, selectedUserId: null, loading: false};export function usersReducer(state = initialState,
action: Action): State { switch (action.type) { ...
IndexedDB IndexedDB is a low-level API for client-side storage of
significant amounts of structured data (key-value pairs), including files/blobs.
API uses indexes to enable high-performance searches of this data. Web Storage - useful for smaller data.
IndexedDB is built on a transactional database model – everything you do in IndexedDB always happens in the context of a transaction.
The IndexedDB API is mostly asynchronous – you have to pass a callback function.
IndexedDB uses DOM events to notify you when results are available - type prop ("success" or "error").
Example: @Effect and IndexedDBconstructor(private actions$: Actions, private db: Database) { }@Effect({ dispatch: false }) openDB$: Observable<any> = defer(() => { return this.db.open('books_app'); });@Effect() loadCollection$: Observable<Action> = this.actions$ .ofType(collection.LOAD) .startWith(new collection.LoadAction()) .switchMap(() => this.db.query('books').toArray() .map((books: Book[]) => new collection.LoadSuccessAction(books)) .catch(error => of( new collection.LoadFailAction(error))) );
@Effect() addBookToCollection$: Observable<Action> = this.actions$ .ofType(collection.ADD_BOOK) .map((action: collection.AddBookAction) => action.payload) .mergeMap(book => this.db.insert('books', [ book ]) .map(() => new collection.AddBookSuccessAction(book)) .catch(() => of( new collection.AddBookFailAction(book))) );
Example: @Effect and IndexedDB
@Effect()
removeBookFromCollection$: Observable<Action> = this.actions$ .ofType(collection.REMOVE_BOOK) .map((action: collection.RemoveBookAction) => action.payload) .mergeMap(book => this.db.executeWrite('books', 'delete', [book.id]) .map(() => new collection.RemoveBookSuccessAction(book)) .catch(() => of( new collection.RemoveBookFailAction(book))) );
Example: @Effect and IndexedDB
export interface RootState { ui: fromUi.State; router: fromRouter.RouterState;}export interface ReducersMap { [key: string]: ActionReducer<any>; }const reducers: ReducersMap = { ui: fromUi.reducer, router: fromRouter.routerReducer};const devProdReducers: ReducersMap = { developmentReducer: compose(storeFreeze, combineReducers)(reducers), productionReducer: combineReducers(reducers) }
How to Lazy Load Reducers? - I
export function addReducer<S>(name: string, reducer: ActionReducer<S>): void { reducers[name] = reducer; devProdReducers['developmentReducer'] = compose(storeFreeze, combineReducers)(reducers); devProdReducers['productionReducer'] = combineReducers(reducers);}export function rootReducer(state: any, action: any) { if (environment.production) { return devProdReducers.productionReducer(state, action) } else { return devProdReducers.developmentReducer(state,action) }}
How to Lazy Load Reducers? - II
This works happily with AOT!
in lazy loaded module (users/user.module.ts):
@NgModule({ ... })export class UserModule { constructor() { addReducer<UserState>('users', usersReducer); }}export interface RootState extends OldRootState { users: UserState;}
Then everywhere in lazy loaded module import augmented RootState from that lazy loaded module (from users/user.module.ts, not from root.reducer.ts).
Adding New Lazy Loaded Reducer
Where to Find the Demo Code?
60
Angular and NGRX demos are available @GitHub:
https://github.com/iproduct/course-angular
ipt-knowledge-manager – NGRX, Reselect, RxJS @Effects, modules lazy loading, AOT
angular2-change-detection-demos – modified from https://github.com/thoughtram/angular2-change-detection-demos
ngrx-example-app – NGRX official demo from https://github.com/ngrx/example-app, IndexedDB
Thank’s for Your Attention!
61
Trayan Iliev
CEO of IPT – Intellectual Products & Technologies
http://iproduct.org/
http://robolearn.org/
https://github.com/iproduct
https://twitter.com/trayaniliev
https://www.facebook.com/IPT.EACAD
https://plus.google.com/+IproductOrg