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

What Groups By in Vegas, Stays in Vegas

Sam Julien
September 05, 2019

What Groups By in Vegas, Stays in Vegas

What if I told you that one of the most useful operators in RxJS is also one you probably have never heard of? In this talk, you'll meet the quiet workhorse called "groupBy."

Don't let its understated nature fool you -- groupBy can change your life! The groupBy operator thrives in heavily interactive UIs, like when users rapidly favorite a bunch of items in a list. When combined with some of the other operators like concatMap, groupBy really shines.

In this talk, you'll learn how to harness its power and take your reactive code to the next level. Using a framework-agnostic approach, you'll see how to use groupBy to give users an excellent experience. You'll also learn the *why* behind when to use groupBy and common pitfalls to avoid with it. Of course, you'll also leave with sample code you can take back to work and start building from right away. Viva Las groupBy!

Sam Julien

September 05, 2019
Tweet

More Decks by Sam Julien

Other Decks in Technology

Transcript

  1. View Slide

  2. WHAT GROUPS BY IN
    VEGAS, STAYS IN VEGAS
    Mike Ryan | Sam Julien

    View Slide

  3. @mikeryandev @samjulien
    COMPLEX UI
    @mikeryandev @samjulien

    View Slide

  4. @mikeryandev @samjulien
    HIGHLY INTERACTIVE UI
    @mikeryandev @samjulien

    View Slide

  5. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  6. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  7. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  8. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  9. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  10. @mikeryandev @samjulien
    MIKE RYAN
    @mikeryandev

    View Slide

  11. @mikeryandev @samjulien
    SOFTWARE ARCHITECT AT SYNAPSE
    MIKE RYAN
    @mikeryandev

    View Slide

  12. @mikeryandev @samjulien
    SOFTWARE ARCHITECT AT SYNAPSE
    GOOGLE DEVELOPER EXPERT
    MIKE RYAN
    @mikeryandev

    View Slide

  13. @mikeryandev @samjulien
    SOFTWARE ARCHITECT AT SYNAPSE
    GOOGLE DEVELOPER EXPERT
    NGRX CORE TEAM
    MIKE RYAN
    @mikeryandev

    View Slide

  14. @mikeryandev @samjulien
    SAM JULIEN
    @samjulien

    View Slide

  15. @mikeryandev @samjulien
    SAM JULIEN
    @samjulien
    DEVREL AT AUTH0

    View Slide

  16. @mikeryandev @samjulien
    SAM JULIEN
    @samjulien
    DEVREL AT AUTH0
    GDE & ANGULAR COLLABORATOR

    View Slide

  17. @mikeryandev @samjulien
    SAM JULIEN
    @samjulien
    DEVREL AT AUTH0
    GDE & ANGULAR COLLABORATOR
    UPGRADINGANGULARJS.COM

    View Slide

  18. @mikeryandev @samjulien
    WARNING
    THIS IS ADVANCED
    @mikeryandev @samjulien

    View Slide

  19. @mikeryandev @samjulien

    View Slide

  20. @mikeryandev @samjulien

    View Slide

  21. @mikeryandev @samjulien

    View Slide

  22. @mikeryandev @samjulien

    View Slide

  23. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  24. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  25. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  26. @mikeryandev @samjulien
    DISPATCHER PATTERN

    View Slide

  27. @mikeryandev @samjulien
    UI COMPONENTS

    View Slide

  28. @mikeryandev @samjulien
    UI COMPONENTS
    ACTIONS

    View Slide

  29. @mikeryandev @samjulien
    UI COMPONENTS
    DISPATCHER
    ACTIONS

    View Slide

  30. @mikeryandev @samjulien
    UI COMPONENTS
    DISPATCHER
    ACTIONS API

    View Slide

  31. @mikeryandev @samjulien
    UI COMPONENTS
    DISPATCHER
    ACTIONS API

    View Slide

  32. @mikeryandev @samjulien

    View Slide

  33. @mikeryandev @samjulien

    View Slide

  34. @mikeryandev @samjulien
    const dispatcher = new Subject();
    @mikeryandev @samjulien

    View Slide

  35. @mikeryandev @samjulien
    const movie1$: Observable = fromEvent(button1, ‘click');
    movie1$.subscribe(() => dispatcher.next({ movieId: 1 }));
    @mikeryandev @samjulien

    View Slide

  36. @mikeryandev @samjulien
    const movie1$: Observable = fromEvent(button1, ‘click');
    movie1$.subscribe(() => dispatcher.next({ movieId: 1 }));
    @mikeryandev @samjulien

    View Slide

  37. @mikeryandev @samjulien
    const movie1$: Observable = fromEvent(button1, ‘click');
    movie1$.subscribe(() => dispatcher.next({ movieId: 1 }));
    @mikeryandev @samjulien

    View Slide

  38. @mikeryandev @samjulien
    HOW DO WE MAP THE BUTTON
    CLICKS TO THE ENDPOINT CALLS?
    @mikeryandev @samjulien

    View Slide

  39. @mikeryandev @samjulien
    const actions$ = dispatcher.asObservable().pipe(
    tap(({ movieId }) => setButtonEmoji(movieId)),
    ???(movie => toggleStatus(movie.movieId))
    );
    @mikeryandev @samjulien

    View Slide

  40. @mikeryandev @samjulien
    const actions$ = dispatcher.asObservable().pipe(
    tap(({ movieId }) => setButtonEmoji(movieId)),
    ???(movie => toggleStatus(movie.movieId))
    );
    @mikeryandev @samjulien

    View Slide

  41. @mikeryandev @samjulien
    const actions$ = dispatcher.asObservable().pipe(
    tap(({ movieId }) => setButtonEmoji(movieId)),
    ???(movie => toggleStatus(movie.movieId))
    );
    @mikeryandev @samjulien

    View Slide

  42. @mikeryandev @samjulien
    const actions$ = dispatcher.asObservable().pipe(
    tap(({ movieId }) => setButtonEmoji(movieId)),
    ???(movie => toggleStatus(movie.movieId))
    );
    @mikeryandev @samjulien

    View Slide

  43. @mikeryandev @samjulien
    MAPPING OPERATORS
    @mikeryandev @samjulien

    View Slide

  44. @mikeryandev @samjulien
    SORTING PACKAGES
    @mikeryandev @samjulien

    View Slide

  45. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  46. @mikeryandev @samjulien

    View Slide

  47. @mikeryandev @samjulien

    View Slide

  48. @mikeryandev @samjulien
    MERGEMAP
    @mikeryandev @samjulien

    View Slide

  49. @mikeryandev @samjulien
    mergeMap

    View Slide

  50. @mikeryandev @samjulien
    mergeMap

    View Slide

  51. @mikeryandev @samjulien
    const actions$ = dispatcher.asObservable().pipe(
    tap(({ movieId }) => setButtonEmoji(movieId)),
    mergeMap(movie => toggleStatus(movie.movieId))
    );
    @mikeryandev @samjulien

    View Slide

  52. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  53. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  54. @mikeryandev @samjulien
    WHAT HAPPENS WHEN WE
    CLICK TOO FAST?

    View Slide

  55. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  56. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  57. @mikeryandev @samjulien
    RACE CONDITIONS
    @mikeryandev @samjulien

    View Slide

  58. @mikeryandev @samjulien
    CONCATMAP
    @mikeryandev @samjulien

    View Slide

  59. @mikeryandev @samjulien
    concatMap

    View Slide

  60. @mikeryandev @samjulien
    concatMap

    View Slide

  61. @mikeryandev @samjulien
    @mikeryandev @samjulien
    const actions$ = dispatcher.asObservable().pipe(
    tap(({ movieId }) => setButtonEmoji(movieId)),
    concatMap(movie => toggleStatus(movie.movieId))
    );

    View Slide

  62. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  63. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  64. @mikeryandev @samjulien
    BACK PRESSURE
    @mikeryandev @samjulien

    View Slide

  65. @mikeryandev @samjulien
    REQUESTS > BUFFER SIZE
    @mikeryandev @samjulien

    View Slide

  66. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  67. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  68. @mikeryandev @samjulien
    SWITCHMAP
    @mikeryandev @samjulien

    View Slide

  69. @mikeryandev @samjulien
    switchMap

    View Slide

  70. @mikeryandev @samjulien
    switchMap

    View Slide

  71. @mikeryandev @samjulien
    @mikeryandev @samjulien
    const actions$ = dispatcher.asObservable().pipe(
    tap(({ movieId }) => setButtonEmoji(movieId)),
    switchMap(movie => toggleStatus(movie.movieId))
    );

    View Slide

  72. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  73. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  74. @mikeryandev @samjulien
    WAIT A SECOND…
    @mikeryandev @samjulien

    View Slide

  75. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  76. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  77. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  78. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  79. @mikeryandev @samjulien
    STRANGELY, THIS MAKES SENSE…
    @mikeryandev @samjulien

    View Slide

  80. @mikeryandev @samjulien
    @mikeryandev @samjulien
    const actions$ = dispatcher.asObservable().pipe(
    tap(({ movieId }) => setButtonEmoji(movieId)),
    switchMap(movie => toggleStatus(movie.movieId))
    );

    View Slide

  81. @mikeryandev @samjulien
    @mikeryandev @samjulien
    const actions$ = dispatcher.asObservable().pipe(
    tap(({ movieId }) => setButtonEmoji(movieId)),
    switchMap(movie => toggleStatus(movie.movieId))
    );

    View Slide

  82. @mikeryandev @samjulien
    GROUPBY
    @mikeryandev @samjulien

    View Slide

  83. @mikeryandev @samjulien
    concatMap
    groupBy

    View Slide

  84. @mikeryandev @samjulien
    concatMap
    groupBy

    View Slide

  85. @mikeryandev @samjulien
    concatMap
    groupBy

    View Slide

  86. @mikeryandev @samjulien
    concatMap
    groupBy

    View Slide

  87. @mikeryandev @samjulien
    switchMap
    groupBy

    View Slide

  88. @mikeryandev @samjulien
    switchMap
    groupBy

    View Slide

  89. @mikeryandev @samjulien
    switchMap
    groupBy

    View Slide

  90. @mikeryandev @samjulien
    switchMap
    groupBy

    View Slide

  91. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  92. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  93. @mikeryandev @samjulien
    WHAT ABOUT BOTH AT ONCE?
    @mikeryandev @samjulien

    View Slide

  94. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  95. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  96. @mikeryandev @samjulien
    HOW DO WE DO IT?
    @mikeryandev @samjulien

    View Slide

  97. @mikeryandev @samjulien
    HIGHER ORDER
    OBSERVABLES
    @mikeryandev @samjulien

    View Slide

  98. [, , ]
    @mikeryandev @samjulien

    View Slide

  99. [[, , ], [, , ], [, , ]]
    @mikeryandev @samjulien

    View Slide

  100. [[, , ], [, , ], [, , ]]
    array.flat()
    [, , , , , , , , ]
    @mikeryandev @samjulien

    View Slide

  101. of(, , )
    @mikeryandev @samjulien

    View Slide

  102. of(of(, , ), of(, , ), of(, , ))
    @mikeryandev @samjulien

    View Slide

  103. of(of(, , ), of(, , ), of(, , ))
    observable$.pipe(mergeAll())
    of(, , , , , , , , )
    @mikeryandev @samjulien

    View Slide

  104. observable$.pipe(
    map(movie => toggleStatus(movie)),
    mergeAll()
    );
    observable$.pipe(
    mergeMap(movie => toggleStatus(movie))
    );
    @mikeryandev @samjulien

    View Slide

  105. GROUPBY
    BEST OPERATOR EVER
    @mikeryandev @samjulien

    View Slide

  106. packages$.pipe(groupBy(package => package.color));
    COMPLETE
    COMPLETE
    COMPLETE COMPLETE
    @mikeryandev @samjulien

    View Slide

  107. packages$.pipe(
    groupBy(package => package.color),
    mergeAll()
    );
    COMPLETE COMPLETE
    @mikeryandev @samjulien

    View Slide

  108. packages$.pipe(
    groupBy(package => package.color),
    mergeAll()
    );
    @mikeryandev @samjulien

    View Slide

  109. actions$.pipe(
    groupBy(action => action.movieId),
    mergeAll()
    );
    @mikeryandev @samjulien

    View Slide

  110. actions$.pipe(
    groupBy(action => action.movieId),
    map(actionsByMovieId$ => {
    return actionsByMovieId$.pipe(
    switchMap(action => {
    return toggleStatus(action.movieId);
    })
    );
    }),
    mergeAll()
    );
    @mikeryandev @samjulien

    View Slide

  111. actions$.pipe(
    groupBy(action => action.movieId),
    map(actionsByMovieId$ => {
    return actionsByMovieId$.pipe(
    switchMap(action => {
    return toggleStatus(action.movieId);
    })
    );
    }),
    mergeAll()
    );
    @mikeryandev @samjulien

    View Slide

  112. actions$.pipe(
    groupBy(action => action.movieId),
    map(actionsByMovieId$ => {
    return actionsByMovieId$.pipe(
    switchMap(action => {
    return toggleStatus(action.movieId);
    })
    );
    }),
    mergeAll()
    );
    @mikeryandev @samjulien

    View Slide

  113. actions$.pipe(
    groupBy(action => action.movieId),
    map(actionsByMovieId$ => {
    return actionsByMovieId$.pipe(
    switchMap(action => {
    return toggleStatus(action.movieId);
    })
    );
    }),
    mergeAll()
    );
    @mikeryandev @samjulien

    View Slide

  114. actions$.pipe(
    groupBy(action => action.movieId),
    map(actionsByMovieId$ => {
    return actionsByMovieId$.pipe(
    switchMap(action => {
    return toggleStatus(action.movieId);
    })
    );
    }),
    mergeAll()
    );
    @mikeryandev @samjulien

    View Slide

  115. @mikeryandev @samjulien
    DEMO
    @mikeryandev @samjulien

    View Slide

  116. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  117. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  118. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  119. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  120. @mikeryandev @samjulien
    WE HAVE A PROBLEM…
    @mikeryandev @samjulien

    View Slide

  121. @mikeryandev @samjulien

    View Slide

  122. View Slide

  123. View Slide

  124. View Slide

  125. View Slide

  126. View Slide

  127. View Slide

  128. View Slide

  129. View Slide

  130. @mikeryandev @samjulien
    WE HAVE BUILT A VERY
    EFFICIENT MEMORY LEAK
    @mikeryandev @samjulien

    View Slide

  131. groupBy(
    /* Key Selector */,
    /* Element Selector */,
    /* Duration Selector */,
    )
    @mikeryandev @samjulien

    View Slide

  132. @mikeryandev @samjulien
    Returns an observable that will clean up the group
    when it notifies or completes.
    DURATION SELECTOR
    @mikeryandev @samjulien

    View Slide

  133. groupBy(
    /* Key Selector */,
    /* Element Selector */,
    /* Duration Selector */,
    )
    @mikeryandev @samjulien

    View Slide

  134. groupBy(
    action => action.movieId,
    action => action,
    group$ => /* discard group after 15s of silence */,
    )
    @mikeryandev @samjulien

    View Slide

  135. @mikeryandev @samjulien
    HOW DO WE GET TO 15
    SECONDS OF SILENCE?
    @mikeryandev @samjulien

    View Slide

  136. @mikeryandev @samjulien
    timeoutWith(
    /** Time in MS */,
    /** Observable to Switch To */
    );
    @mikeryandev @samjulien

    View Slide

  137. observable$.pipe(timeoutWith(15000, EMPTY));
    COMPLETE
    COMPLETE
    @mikeryandev @samjulien

    View Slide

  138. @mikeryandev @samjulien
    Returns an observable that will clean up the group
    when it notifies or completes.
    DURATION SELECTOR
    @mikeryandev @samjulien

    View Slide

  139. @mikeryandev @samjulien
    Returns an observable that will clean up the group
    when it notifies or completes.
    DURATION SELECTOR
    @mikeryandev @samjulien

    View Slide

  140. observable$.pipe(timeoutWith(15000, EMPTY));
    COMPLETE
    COMPLETE
    @mikeryandev @samjulien

    View Slide

  141. observable$.pipe(timeoutWith(15000, EMPTY));
    COMPLETE
    COMPLETE
    Notification Completion
    @mikeryandev @samjulien

    View Slide

  142. @mikeryandev @samjulien
    DEMO
    @mikeryandev @samjulien

    View Slide

  143. @mikeryandev @samjulien

    View Slide

  144. @mikeryandev @samjulien

    View Slide

  145. @mikeryandev @samjulien
    WE HAVE REBUILT
    MERGEMAP
    @mikeryandev @samjulien

    View Slide

  146. observable$.pipe(???);
    @mikeryandev @samjulien
    COMPLETE
    COMPLETE

    View Slide

  147. @mikeryandev @samjulien
    HOW DO WE GET RID OF
    THE NOTIFICATIONS?
    @mikeryandev @samjulien

    View Slide

  148. observable$.pipe(ignoreElements());
    COMPLETE COMPLETE
    @mikeryandev @samjulien

    View Slide

  149. observable$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    );
    @mikeryandev @samjulien

    View Slide

  150. observable$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    );
    @mikeryandev @samjulien

    View Slide

  151. observable$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    );
    @mikeryandev @samjulien

    View Slide

  152. observable$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    );
    COMPLETE
    COMPLETE
    @mikeryandev @samjulien

    View Slide

  153. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    )
    );
    @mikeryandev @samjulien

    View Slide

  154. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    )
    );
    @mikeryandev @samjulien

    View Slide

  155. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    )
    );
    @mikeryandev @samjulien

    View Slide

  156. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    )
    );
    @mikeryandev @samjulien

    View Slide

  157. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    )
    );
    @mikeryandev @samjulien

    View Slide

  158. @mikeryandev @samjulien
    NO MORE HIGHLY EFFICIENT
    MEMORY LEAKS
    @mikeryandev @samjulien

    View Slide

  159. actions$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    );
    @mikeryandev @samjulien

    View Slide

  160. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    ),
    mergeAll()
    );

    View Slide

  161. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(

    View Slide

  162. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(

    View Slide

  163. actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    ),
    mergeAll()
    );

    View Slide

  164. actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    ),
    mergeAll()
    );

    View Slide

  165. )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    ),
    mergeAll()
    );

    View Slide

  166. )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    ),
    mergeAll()
    );

    View Slide

  167. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    ),
    mergeAll()
    );

    View Slide

  168. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    ),
    mergeAll()
    );
    Group By Movie ID

    View Slide

  169. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    ),
    mergeAll()
    );
    Save Status

    View Slide

  170. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    ),
    mergeAll()
    );
    Flatten Everything

    View Slide

  171. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    map(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    ),
    mergeAll()
    );

    View Slide

  172. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    mergeMap(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    )
    );

    View Slide

  173. actions$.pipe(
    groupBy(
    action => action.movieId,
    action => action,
    actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    timeoutWith(15000, EMPTY),
    ignoreElements()
    )
    ),
    mergeMap(actionsByMovieId$ =>
    actionsByMovieId$.pipe(
    switchMap(action => toggleStatus(action.movieId))
    )
    )
    );

    View Slide

  174. @mikeryandev @samjulien
    DEMO
    @mikeryandev @samjulien

    View Slide

  175. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  176. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  177. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  178. @mikeryandev @samjulien
    @mikeryandev @samjulien

    View Slide

  179. @mikeryandev @samjulien
    UI COMPONENTS
    DISPATCHER
    ACTIONS API

    View Slide

  180. @mikeryandev @samjulien
    UI COMPONENTS
    DISPATCHER
    ACTIONS API
    POTENTIAL PERFORMANCE
    BOTTLENECKS

    View Slide

  181. @mikeryandev @samjulien
    UI COMPONENTS
    DISPATCHER
    ACTIONS API
    API API

    View Slide

  182. @mikeryandev @samjulien
    CUSTOM OPERATOR…?
    @mikeryandev @samjulien

    View Slide

  183. @mikeryandev @samjulien
    samj.im/rxjs-live
    @mikeryandev @samjulien

    View Slide

  184. @mikeryandev @samjulien
    samj.im/rxjs-live
    @mikeryandev @samjulien
    THANK YOU!

    View Slide