ngrx apps in depth

61
June 15, 2017 IPT – Intellectual Products & Technologies NGRX Apps in Depth – RxJS, Reselect, Router, IndexedDB, @Effects Trayan Iliev [email protected] http://iproduct.org Copyright © 2003-2017 IPT - Intellectual Products & Technologies

Upload: trayan-iliev

Post on 22-Jan-2018

350 views

Category:

Software


1 download

TRANSCRIPT

Page 1: 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

Page 2: NGRX Apps in Depth

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

Page 3: NGRX Apps in Depth

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.

Page 4: NGRX Apps in Depth

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

Page 5: NGRX Apps in Depth

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

Page 6: NGRX Apps in Depth

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?

Page 7: NGRX Apps in Depth

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

Page 8: NGRX Apps in Depth

Lambda Architecture - I

8

https://commons.wikimedia.org/w/index.php?curid=34963986, By Textractor - Own work, CC BY-SA 4

Page 9: NGRX Apps in Depth

Lambda Architecture - II

9

https://commons.wikimedia.org/w/index.php?curid=34963987, By Textractor - Own work, CC BY-SA 4

Page 10: NGRX Apps in Depth

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.

Page 11: NGRX Apps in Depth

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

Page 12: NGRX Apps in Depth

Lambda Architecture: Projects - I

12

Page 13: NGRX Apps in Depth

Direct Acyclic Graphs - DAG

13

Page 14: NGRX Apps in Depth

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?

Page 15: NGRX Apps in Depth

Synchronous vs. Asynchronous IO

15

DB

Synchronous

A

A

B

B

DB

Asynchronous

A

B

C

D

A

B

C

D

Page 16: NGRX Apps in Depth

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

Page 17: NGRX Apps in Depth

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 )

Page 18: NGRX Apps in Depth

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.

Page 19: NGRX Apps in Depth

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));

Page 20: NGRX Apps in Depth

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

Page 21: NGRX Apps in Depth

Reactive Manifesto

21

[http://www.reactivemanifesto.org]

Page 22: NGRX Apps in Depth

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

Page 23: NGRX Apps in Depth

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 ...

Page 24: NGRX Apps in Depth

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

Page 25: NGRX Apps in Depth

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;}

Page 26: NGRX Apps in Depth

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

Page 27: NGRX Apps in Depth

27

RxJS Resources

RxMarbles:

http://rxmarbles.com/

RxJS Coans:https://github.com/Reactive-Extensions/RxJSKoans

Learn RxJS:https://www.learnrxjs.io/

Page 28: NGRX Apps in Depth

Source: https://github.com/ReactiveX/rxjs/blob/master/doc/asset/marble-diagram-anatomy.svg, Apache v2

Anatomy of Rx Operator

Page 29: NGRX Apps in Depth

Example: combineLatest()

29

https://projectreactor.io/core/docs/api/, Apache Software License 2.0

Page 30: NGRX Apps in Depth

Example: switchMap()

30

http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html, Apache Software License 2.0

Page 31: NGRX Apps in Depth

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.

Page 32: NGRX Apps in Depth

Converting Cold to Hot Stream

32

Page 33: NGRX Apps in Depth

Flux Design Pattern

Source: Flux in GitHub, https://github.com/facebook/flux, License: BSD 3-clause "New" License

Page 34: NGRX Apps in Depth

Linear flow:Source: @ngrx/store in GitHub, https://gist.github.com/btroncone/a6e4347326749f938510

Redux Design Pattern

Page 35: NGRX Apps in Depth

Source: RxJava 2 API documentation, http://reactivex.io/RxJava/2.x/javadoc/

Redux == Rx Scan Opearator

Page 36: NGRX Apps in Depth

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;

Page 37: NGRX Apps in Depth

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;

Page 38: NGRX Apps in Depth

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

Page 39: NGRX Apps in Depth

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

Page 40: NGRX Apps in Depth

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),

... ] ...

Page 41: NGRX Apps in Depth

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};

Page 42: NGRX Apps in Depth

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

Page 43: NGRX Apps in Depth

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); } }

Page 44: NGRX Apps in Depth

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);

Page 45: NGRX Apps in Depth

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);

Page 46: NGRX Apps in Depth

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.

Page 47: NGRX Apps in Depth

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)}});

Page 48: NGRX Apps in Depth

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}}

Page 49: NGRX Apps in Depth

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])));

Page 50: NGRX Apps in Depth

Router Integration, MD, PrimeNG

Page 51: NGRX Apps in Depth

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.

Page 52: NGRX Apps in Depth

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) { ...

Page 53: NGRX Apps in Depth

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").

Page 54: NGRX Apps in Depth

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))) );

Page 55: NGRX Apps in Depth

@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

Page 56: NGRX Apps in Depth

@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

Page 57: NGRX Apps in Depth

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

Page 58: NGRX Apps in Depth

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!

Page 59: NGRX Apps in Depth

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

Page 60: NGRX Apps in Depth

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

Page 61: NGRX Apps in Depth

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