Upgrade to Pro — share decks privately, control downloads, hide ads and more …

NGRX Apps in Depth

NGRX Apps in Depth

Presentation from Angular Sofia Meetup event focuses on integration between state-of-the-art Angular, component libraries and supporting technologies, necessary to build a scalable and performant single-page apps. Topics include:
- Composing NGRX Reducers, Selectors and Middleware;
- Computing derived data using Reselect-style memoization with RxJS;
- NGRX Router integration;
- Normalization/denormalization and keeping data locally in IndexedDB;
- Processing Observable (hot) streams of async actions, and isolating the side effects using @Effect decorator with NGRX/RxJS reactive transforms;
- Integration of Material Design with third party component libraries like PrimeNG;
- more: lazy loading, AOT...

Trayan Iliev

June 15, 2017
Tweet

More Decks by Trayan Iliev

Other Decks in Programming

Transcript

  1. 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
  2. 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. 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.
  4. 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
  5. 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
  6. 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?
  7. 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
  8. 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.
  9. 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
  10. 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?
  11. 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
  12. 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 )
  13. 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.
  14. 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));
  15. 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
  16. 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
  17. 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 ...
  18. 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
  19. 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; }
  20. 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
  21. 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.
  22. 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;
  23. 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;
  24. 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
  25. 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
  26. 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), ... ] ...
  27. 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 };
  28. 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
  29. 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); } }
  30. Composing User Selectors  in users/user.selectors.ts: // User state selectors

    export 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 selectors export const getUsersState = (state: RootState) => state.users; export const getUsers = createSelector(getUsersState, getAll);
  31. Using Reselect  in users/user.selectors.ts: // User state selectors export

    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 selectors export const getUsersState = (state: RootState) => state.users; export const getUsers = createSelector(getUsersState, getAll);
  32. 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.
  33. Computing Derived Data: Reselect import { createSelector } from 'reselect'

    const getVisibilityFilter = (state) => state.visibilityFilter const getTodos = (state) => state.todos export 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) }});
  34. 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 }}
  35. 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])));
  36. 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.
  37. 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) { ...
  38. 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").
  39. Example: @Effect and IndexedDB constructor(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))) );
  40. @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
  41. @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
  42. 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
  43. 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!
  44.  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
  45. 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
  46. 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