NGRX Apps in Depth – RxJS, Reselect, Router, IndexedDB...

48
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

Transcript of NGRX Apps in Depth – RxJS, Reselect, Router, IndexedDB...

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

2

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?

3

Angular and NGRX demos are available @GitHub:https://github.com/iproduct/course-angular

ipt-knowledge-manager – NGRX, Reselect, RxJS @Effects, modules lazy loading, AOT

ngrx-example-app – NGRX official demo (including IndexDB from: https://github.com/ngrx/platform/tree/master/example-app

Data / Event / Message Streams

4

“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

5

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:

6

“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

7

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

Lambda Architecture - II

8

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

Lambda Architecture - III

9

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

10

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

11

Direct Acyclic Graphs - DAG

12

13

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

14

DB

Synchronous

A

A

B

B

DB

Asynchronous

A

B

C

D

A

B

C

D

Reactive Programming Specs

15

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

ES7 Observable Spec (RxJS 5)

16

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

17

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

18

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

20

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

Example: switchMap()

21

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

Hot and Cold Event Streams

22

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

23

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

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

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.forRoot(reducers), StoreRouterConnectingModule,

EffectsModule.forRoot([RoutingEffects]), !environment.production ?

StoreDevtoolsModule.instrument({ maxAge: 50 }) : []

DBModule.provideDB(schema), ...

]

...

NGRX: Defining State & Reducers in root.reducer.ts:

export interface RootState { ui: fromUi.State; router: fromRouter.RouterReducerState; users: fromUsers.State; tests: fromTests.State;}

export const reducers: ActionReducerMap<RootState> = { ui: fromUi.reducer, router: fromRouter.routerReducer, users: fromUsers.reducer, tests: fromTests.reducer};

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

in lazy loaded module (users/user.module.ts):@NgModule({ imports: [ StoreModule.forFeature('users', usersReducer), EffectsModule.forFeature([UserEffects]) ...],

...})export class UserModule {...}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?

47

Angular and NGRX demos are available @GitHub:https://github.com/iproduct/course-angular

ipt-knowledge-manager – NGRX, Reselect, RxJS @Effects, modules lazy loading, AOT

ngrx-example-app – NGRX official demo (including IndexDB from: https://github.com/ngrx/platform/tree/master/example-app

Thank’s for Your Attention!

48

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