Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Angular2 & ngrx/store: Game of States

4,241 views

Published on

For years i wanted a robust solution to organize code:
Eliminate messy code
Organize structure
Set strict rules for apps
Write simple tests that cover exactly what i need
Write Less, Think Less - Do More
I just wanted a simple hands-on philosophy in my code.
Then came NgRx/store.
This is an intro to ngrx/store and its echo-system with a usecase of Echoes Player (ng2) open source player developed with angular 2 and ngrx/store.

Published in: Internet

Angular2 & ngrx/store: Game of States

  1. 1. NG2 & NgRx/Store: Oren Farhi @Orizens
  2. 2. hi! I AM Oren Farhi Senior Javascript Engineer Freelance Javascript Consultant & Tutor You can find me at: orizens.com github.com/orizens @orizens
  3. 3. There’s Gotta Be Something Better Redux for Angular 2 with NgRx/Store
  4. 4. - Problems of State Management - What is NgRx/Store - Usecase App - Echoes Player NgRx/Store In Echoes Player Agenda
  5. 5. Problems With State Management
  6. 6. The State Of The App? ◦ Where? ◦ Share
  7. 7. Current State Of The App UI ⇔ Model
  8. 8. Solution: ngrx/store (benefits) 1. App State is Predictable 2. Separate Logics From UI 3. Optimization in Performance 4. Easy To Test 5. Time Travel (logs) 6. Route & State 7. Promotes Stateless Components
  9. 9. Ngrx/Store State Management Made Easy
  10. 10. Example App: Echoes Player - http://echotu.be
  11. 11. Store Mother Of All States
  12. 12. Store is an observable “DB” Store { Data } { Object } [ Array ] number string { Data } { Data } { Data }
  13. 13. Store - json object... bootstrap(App, [ provideStore({ videos: videosReducer }) ]);
  14. 14. Reducers - CRUD the Store with Pure Functions (Observable) const videos = function(state = [], action) => { switch (action.type) { case 'ADD_VIDEO': return state.concat( action.payload ); default: return state; } }
  15. 15. Reducers - CRUD the Store with Pure Functions (Observable) let sum = [1,2,3] sum.reduce( (result, value) => result + value, 0 );
  16. 16. Action - dispatch an update to State through Reducers addVideo(media) { this.store.dispatch({ type: ‘ADD_VIDEO’, payload: media }); }
  17. 17. Subscribe And Observe - data projection, slice store’s data @Component({ selector: ‘youtube-player’ }) constructor (store) { this.player$ = store.select(store => store.player); this.player$.subscribe(player => { this.isFullscreen = player.isFullscreen }); }
  18. 18. Subscribe - data projection - listen with Observable @Component({ selector: ‘youtube-player’ }) constructor (store) { this.player$ = store.select(store => store.player); this.player$.subscribe(player => { this.isFullscreen = player.isFullscreen }); }
  19. 19. Subscribe And Observe - data projection, slice store’s data let initialPlayerState = { mediaId: { videoId: 'NONE' }, media: { snippet: { title: 'No Media Yet' } }, showPlayer: true, playerState: 0, isFullscreen: false }
  20. 20. UI Subscribe - “async” pipe - data projection to view <section class="player"> <player-controls [player]="player$ | async" (action)="handleControlAction($event)" ></player-controls> </section> let tempObv = player$.subscribe(player => this.player = player); tempObv.unsubscribe(); // when view is destroyed
  21. 21. One Way Data Flow Component Reducer Store Action
  22. 22. NgRx/Store in Echoes Player Store & The Youtube Videos Component
  23. 23. Store In Echoes Player export interface EchoesState { videos: EchoesVideos; player: YoutubePlayerState; nowPlaylist: YoutubeMediaPlaylist; user: UserProfile; search: PlayerSearch; }
  24. 24. Youtube Videos - SMART Component <youtube-videos>
  25. 25. Youtube Videos - 2 Stateless Components (DUMB) <youtube-videos> <youtube-list> <player-search>
  26. 26. Youtube Videos Component (SMART) @Component({ selector: 'youtube-videos', directives: [ PlayerSearchComponent, YoutubeList], template: ` <player-search [query]="search$ | async" (change)="resetPageToken()" (search)="search($event)" ></player-search> <youtube-list [list]="videos$ | async" (play)="playSelectedVideo($event)" (queue)="queueSelectedVideo($event)" ></youtube-list>` }) export class YoutubeVideos implements OnInit {}
  27. 27. Youtube Videos Component (SMART) @Component({ selector: 'youtube-videos', directives: [ PlayerSearchComponent, YoutubeList], template: ` <player-search [query]="search$ | async" (change)="resetPageToken()" (search)="search($event)" ></player-search> <youtube-list [list]="videos$ | async" (play)="playSelectedVideo($event)" (queue)="queueSelectedVideo($event)" ></youtube-list>` }) export class YoutubeVideos implements OnInit {}
  28. 28. Performance Boost For Components @Component({ selector: 'youtube-list', template: ` <youtube-media *ngFor="let media of list" [media]="media" (play)="playSelectedVideo(media)" (queue)="queueSelectedVideo(media)" (add)="addVideo(media)"> </youtube-media> `, directives: [NgFor, YoutubeMedia ], changeDetection: ChangeDetectionStrategy.OnPush }) export class YoutubeList { @Input() list: Array<any>; }
  29. 29. Youtube Videos Component (SMART) @Component({ selector: 'youtube-videos', directives: [ PlayerSearchComponent, YoutubeList], template: ` <player-search [query]="search$ | async" (change)="resetPageToken()" (search)="search($event)" ></player-search> <youtube-list [list]="videos$ | async" (play)="playSelectedVideo($event)" (queue)="queueSelectedVideo($event)" ></youtube-list>` }) export class YoutubeVideos implements OnInit {}
  30. 30. Youtube Videos - Connecting to Store export class YoutubeVideos implements OnInit { videos$: Observable<EchoesVideos>; search$: Observable<PlayerSearch>; constructor( private youtubeSearch: YoutubeSearch, private nowPlaylistService: NowPlaylistService, private store: Store<EchoesState>, public youtubePlayer: YoutubePlayerService) { this.videos$ = store.select(state => state.videos); this.search$ = store.select(state => state.search); } }
  31. 31. Youtube Videos - Connecting to Store export class YoutubeVideos implements OnInit { videos$: Observable<EchoesVideos>; search$: Observable<PlayerSearch>; constructor( private youtubeSearch: YoutubeSearch, private nowPlaylistService: NowPlaylistService, private store: Store<EchoesState>, public youtubePlayer: YoutubePlayerService) { this.videos$ = store.select(state => state.videos); this.search$ = store.select(state => state.search); } }
  32. 32. Updating the Store @Component({ selector: 'youtube-videos', directives: [ PlayerSearchComponent, YoutubeList], template: ` <player-search [query]="search$ | async" (change)="resetPageToken()" (search)="search($event)" ></player-search> <youtube-list [list]="videos$ | async" (play)="playSelectedVideo($event)" (queue)="queueSelectedVideo($event)" ></youtube-list>` }) export class YoutubeVideos implements OnInit {}
  33. 33. Youtube Videos Component Interaction with store services: - YoutubeSearch - YoutubePlayer - NowPlaylist
  34. 34. Youtube Videos - Updating The Store export class YoutubeVideos implements OnInit { videos$: Observable<EchoesVideos>; playerSearch$: Observable<PlayerSearch>; constructor(){ ... } search (query: string) { if (query.length) { this.youtubeSearch.search(query, false); } } queueSelectedVideo (media: GoogleApiYouTubeSearchResource) { return this.nowPlaylistService.queueVideo(media.id.videoId); } }
  35. 35. Youtube Videos - Updating The Store export class YoutubeVideos implements OnInit { videos$: Observable<EchoesVideos>; playerSearch$: Observable<PlayerSearch>; constructor(){ ... } search (query: string) { if (query.length) { this.youtubeSearch.search(query, false); } } queueSelectedVideo (media: GoogleApiYouTubeSearchResource) { return this.nowPlaylistService.queueVideo(media.id.videoId); } }
  36. 36. Services - dispatch to Store @Injectable() export class NowPlaylistService { constructor(public store: Store<EchoesState>) { this.playlist$ = this.store.select(state => state.nowPlaylist); } queueVideo (mediaId: string) { return this.youtubeVideosInfo.api .list(mediaId).then(response => { this.store.dispatch({ type: QUEUE, //- const imported from reducer payload: response.items[0] }); return response.items[0]; }); } }
  37. 37. NowPlaylist Reducer let initialState = { videos: [], index: '', filter: '' } export const nowPlaylist = (state = initialState, action) => { switch (action.type) { case NowPlaylistActions.QUEUE: return Object.assign( ); default: return state; }
  38. 38. NowPlaylist Reducer let initialState = { videos: [], index: '', filter: '' } export const nowPlaylist = (state = initialState, action) => { switch (action.type) { case NowPlaylistActions.QUEUE: return Object.assign({}, state, { videos: addMedia(state.videos, action.payload) }); default: return state; }
  39. 39. Services - dispatch to Store @Injectable() export class NowPlaylistService { constructor(public store: Store<EchoesState>) { this.playlist$ = this.store.select(state => state.nowPlaylist); } queueVideo (mediaId: string) { return this.youtubeVideosInfo.api .list(mediaId).then(response => { this.store.dispatch({ type: QUEUE, //- const imported from reducer payload: response.items[0] }); return response.items[0]; }); } }
  40. 40. Ngrx/effects Side effects of Actions
  41. 41. Side Effects for Queue Video @Injectable() export class NowPlaylistEffects { constructor( store$, nowPlaylistActions youtubeVideosInfo ){} @Effect() queueVideoReady$ = this.store$ .whenAction(NowPlaylistActions.QUEUE_LOAD_VIDEO) .map<GoogleApiYouTubeSearchResource>(toPayload) .switchMap(media => this.youtubeVideosInfo.fetchVideoData(media.id. videoId) .map(media => this.nowPlaylistActions.queueVideo(media)) .catch(() => Observable.of(this.nowPlaylistActions.queueFailed(media))) ); }
  42. 42. Testing Reducers Testing now playlist
  43. 43. Loading the tested objects import { it, inject, async, describe, expect } from '@angular/core/testing'; import { nowPlaylist, NowPlaylistActions } from './now-playlist'; import { YoutubeMediaItemsMock } from './mocks/youtube.media. items';
  44. 44. Spec - select a video in now playlist it('should select the chosen video', () => { const state = { index: '', videos: [...YoutubeMediaItemsMock], filter: '' }; const actual = nowPlaylist(state, { type: NowPlaylistActions.SELECT, payload: YoutubeMediaItemsMock[0] }); const expected = YoutubeMediaItemsMock[0]; expect(actual.index).toBe(expected.id); });
  45. 45. Spec - set initial state it('should select the chosen video', () => { const state = { index: '', videos: [...YoutubeMediaItemsMock], filter: '' }; const actual = nowPlaylist(state, { type: NowPlaylistActions.SELECT, payload: YoutubeMediaItemsMock[0] }); const expected = YoutubeMediaItemsMock[0]; expect(actual.index).toBe(expected.id); });
  46. 46. Spec - select a video in now playlist it('should select the chosen video', () => { const state = { index: '', videos: [...YoutubeMediaItemsMock], filter: '' }; const actual = nowPlaylist(state, { type: NowPlaylistActions.SELECT, payload: YoutubeMediaItemsMock[0] }); const expected = YoutubeMediaItemsMock[0]; expect(actual.index).toBe(expected.id); });
  47. 47. Spec - select a video in now playlist it('should select the chosen video', () => { const state = { index: '', videos: [...YoutubeMediaItemsMock], filter: '' }; const actual = nowPlaylist(state, { type: NowPlaylistActions.SELECT, payload: YoutubeMediaItemsMock[0] }); const expected = YoutubeMediaItemsMock[0]; expect(actual.index).toBe(expected.id); });
  48. 48. Spec - select a video in now playlist it('should select the chosen video', () => { const state = { index: '', videos: [...YoutubeMediaItemsMock], filter: '' }; const actual = nowPlaylist(state, { type: NowPlaylistActions.SELECT, payload: YoutubeMediaItemsMock[0] }); const expected = YoutubeMediaItemsMock[0]; expect(actual.index).toBe(expected.id); });
  49. 49. Ngrx DevTools
  50. 50. Ngrx DevTools
  51. 51. More NgRx To Explore: ngrx/router ngrx/db ...
  52. 52. Thanks! ANY QUESTIONS? You can find me at @orizens oren@orizens.com http://orizens.com/services NG2 + Ngrx/Store Workshop: Register at http://goo.gl/EJmm7q
  53. 53. CREDITS ◦ Presentation template by SlidesCarnival ◦ http://orizens.com/wp/topics/adding-redux-with- ngrxstore-to-angular-2-part-1/ ◦ http://orizens.com/wp/topics/adding-redux-with- ngrxstore-to-angular2-part-2-testing-reducers/ ◦ http://orizens.com/wp/topics/angular-2-ngrxstore- the-ngmodel-in-between-use-case-from-angular-1/ ◦ Comprehensive Introduction to NgRx/Store by btroncone ◦ Reactive Angular With Ngrx/Store by Rob Warmald ◦ https://github.com/ngrx/store ◦

×