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

What Groups By in Vegas, Stays in Vegas

7beed3a6fa39e12c9e873b903e4d9244?s=47 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!

7beed3a6fa39e12c9e873b903e4d9244?s=128

Sam Julien

September 05, 2019
Tweet

Transcript

  1. None
  2. WHAT GROUPS BY IN VEGAS, STAYS IN VEGAS Mike Ryan

    | Sam Julien
  3. @mikeryandev @samjulien COMPLEX UI @mikeryandev @samjulien

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

  5. @mikeryandev @samjulien @mikeryandev @samjulien

  6. @mikeryandev @samjulien @mikeryandev @samjulien

  7. @mikeryandev @samjulien @mikeryandev @samjulien

  8. @mikeryandev @samjulien @mikeryandev @samjulien

  9. @mikeryandev @samjulien @mikeryandev @samjulien

  10. @mikeryandev @samjulien MIKE RYAN @mikeryandev

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

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

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

    CORE TEAM MIKE RYAN @mikeryandev
  14. @mikeryandev @samjulien SAM JULIEN @samjulien

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

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

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

    ANGULAR COLLABORATOR UPGRADINGANGULARJS.COM
  18. @mikeryandev @samjulien WARNING THIS IS ADVANCED @mikeryandev @samjulien

  19. @mikeryandev @samjulien

  20. @mikeryandev @samjulien

  21. @mikeryandev @samjulien

  22. @mikeryandev @samjulien

  23. @mikeryandev @samjulien @mikeryandev @samjulien

  24. @mikeryandev @samjulien @mikeryandev @samjulien

  25. @mikeryandev @samjulien @mikeryandev @samjulien

  26. @mikeryandev @samjulien DISPATCHER PATTERN

  27. @mikeryandev @samjulien UI COMPONENTS

  28. @mikeryandev @samjulien UI COMPONENTS ACTIONS

  29. @mikeryandev @samjulien UI COMPONENTS DISPATCHER ACTIONS

  30. @mikeryandev @samjulien UI COMPONENTS DISPATCHER ACTIONS API

  31. @mikeryandev @samjulien UI COMPONENTS DISPATCHER ACTIONS API

  32. @mikeryandev @samjulien

  33. @mikeryandev @samjulien

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

  35. @mikeryandev @samjulien const movie1$: Observable<Event> = fromEvent(button1, ‘click'); movie1$.subscribe(() =>

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

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

    dispatcher.next({ movieId: 1 })); @mikeryandev @samjulien
  38. @mikeryandev @samjulien HOW DO WE MAP THE BUTTON CLICKS TO

    THE ENDPOINT CALLS? @mikeryandev @samjulien
  39. @mikeryandev @samjulien const actions$ = dispatcher.asObservable().pipe( tap(({ movieId }) =>

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

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

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

    setButtonEmoji(movieId)), ???(movie => toggleStatus(movie.movieId)) ); @mikeryandev @samjulien
  43. @mikeryandev @samjulien MAPPING OPERATORS @mikeryandev @samjulien

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

  45. @mikeryandev @samjulien @mikeryandev @samjulien

  46. @mikeryandev @samjulien

  47. @mikeryandev @samjulien

  48. @mikeryandev @samjulien MERGEMAP @mikeryandev @samjulien

  49. @mikeryandev @samjulien mergeMap

  50. @mikeryandev @samjulien mergeMap

  51. @mikeryandev @samjulien const actions$ = dispatcher.asObservable().pipe( tap(({ movieId }) =>

    setButtonEmoji(movieId)), mergeMap(movie => toggleStatus(movie.movieId)) ); @mikeryandev @samjulien
  52. @mikeryandev @samjulien @mikeryandev @samjulien

  53. @mikeryandev @samjulien @mikeryandev @samjulien

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

  55. @mikeryandev @samjulien @mikeryandev @samjulien

  56. @mikeryandev @samjulien @mikeryandev @samjulien

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

  58. @mikeryandev @samjulien CONCATMAP @mikeryandev @samjulien

  59. @mikeryandev @samjulien concatMap

  60. @mikeryandev @samjulien concatMap

  61. @mikeryandev @samjulien @mikeryandev @samjulien const actions$ = dispatcher.asObservable().pipe( tap(({ movieId

    }) => setButtonEmoji(movieId)), concatMap(movie => toggleStatus(movie.movieId)) );
  62. @mikeryandev @samjulien @mikeryandev @samjulien

  63. @mikeryandev @samjulien @mikeryandev @samjulien

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

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

  66. @mikeryandev @samjulien @mikeryandev @samjulien

  67. @mikeryandev @samjulien @mikeryandev @samjulien

  68. @mikeryandev @samjulien SWITCHMAP @mikeryandev @samjulien

  69. @mikeryandev @samjulien switchMap

  70. @mikeryandev @samjulien switchMap

  71. @mikeryandev @samjulien @mikeryandev @samjulien const actions$ = dispatcher.asObservable().pipe( tap(({ movieId

    }) => setButtonEmoji(movieId)), switchMap(movie => toggleStatus(movie.movieId)) );
  72. @mikeryandev @samjulien @mikeryandev @samjulien

  73. @mikeryandev @samjulien @mikeryandev @samjulien

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

  75. @mikeryandev @samjulien @mikeryandev @samjulien

  76. @mikeryandev @samjulien @mikeryandev @samjulien

  77. @mikeryandev @samjulien @mikeryandev @samjulien

  78. @mikeryandev @samjulien @mikeryandev @samjulien

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

  80. @mikeryandev @samjulien @mikeryandev @samjulien const actions$ = dispatcher.asObservable().pipe( tap(({ movieId

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

    }) => setButtonEmoji(movieId)), switchMap(movie => toggleStatus(movie.movieId)) );
  82. @mikeryandev @samjulien GROUPBY @mikeryandev @samjulien

  83. @mikeryandev @samjulien concatMap groupBy

  84. @mikeryandev @samjulien concatMap groupBy

  85. @mikeryandev @samjulien concatMap groupBy

  86. @mikeryandev @samjulien concatMap groupBy

  87. @mikeryandev @samjulien switchMap groupBy

  88. @mikeryandev @samjulien switchMap groupBy

  89. @mikeryandev @samjulien switchMap groupBy

  90. @mikeryandev @samjulien switchMap groupBy

  91. @mikeryandev @samjulien @mikeryandev @samjulien

  92. @mikeryandev @samjulien @mikeryandev @samjulien

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

  94. @mikeryandev @samjulien @mikeryandev @samjulien

  95. @mikeryandev @samjulien @mikeryandev @samjulien

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

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

  98. [, , ] @mikeryandev @samjulien

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

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

    [, , , , , , , , ] @mikeryandev @samjulien
  101. of(, , ) @mikeryandev @samjulien

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

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

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

    ); @mikeryandev @samjulien
  105. GROUPBY BEST OPERATOR EVER @mikeryandev @samjulien

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

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

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

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

  110. actions$.pipe( groupBy(action => action.movieId), map(actionsByMovieId$ => { return actionsByMovieId$.pipe( switchMap(action

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

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

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

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

    => { return toggleStatus(action.movieId); }) ); }), mergeAll() ); @mikeryandev @samjulien
  115. @mikeryandev @samjulien DEMO @mikeryandev @samjulien

  116. @mikeryandev @samjulien @mikeryandev @samjulien

  117. @mikeryandev @samjulien @mikeryandev @samjulien

  118. @mikeryandev @samjulien @mikeryandev @samjulien

  119. @mikeryandev @samjulien @mikeryandev @samjulien

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

  121. @mikeryandev @samjulien

  122. None
  123. None
  124. None
  125. None
  126. None
  127. None
  128. None
  129. None
  130. @mikeryandev @samjulien WE HAVE BUILT A VERY EFFICIENT MEMORY LEAK

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

    Duration Selector */, ) @mikeryandev @samjulien
  132. @mikeryandev @samjulien Returns an observable that will clean up the

    group when it notifies or completes. DURATION SELECTOR @mikeryandev @samjulien
  133. groupBy( /* Key Selector */, /* Element Selector */, /*

    Duration Selector */, ) @mikeryandev @samjulien
  134. groupBy( action => action.movieId, action => action, group$ => /*

    discard group after 15s of silence */, ) @mikeryandev @samjulien
  135. @mikeryandev @samjulien HOW DO WE GET TO 15 SECONDS OF

    SILENCE? @mikeryandev @samjulien
  136. @mikeryandev @samjulien timeoutWith( /** Time in MS */, /** Observable

    to Switch To */ ); @mikeryandev @samjulien
  137. observable$.pipe(timeoutWith(15000, EMPTY)); COMPLETE COMPLETE @mikeryandev @samjulien

  138. @mikeryandev @samjulien Returns an observable that will clean up the

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

    group when it notifies or completes. DURATION SELECTOR @mikeryandev @samjulien
  140. observable$.pipe(timeoutWith(15000, EMPTY)); COMPLETE COMPLETE @mikeryandev @samjulien

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

  142. @mikeryandev @samjulien DEMO @mikeryandev @samjulien

  143. @mikeryandev @samjulien

  144. @mikeryandev @samjulien

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

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

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

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

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

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

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

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

  153. actions$.pipe( groupBy( action => action.movieId, action => action, actionsByMovieId$ =>

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

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

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

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

    actionsByMovieId$.pipe( timeoutWith(15000, EMPTY), ignoreElements() ) ) ); @mikeryandev @samjulien
  158. @mikeryandev @samjulien NO MORE HIGHLY EFFICIENT MEMORY LEAKS @mikeryandev @samjulien

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

  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() );
  161. actions$.pipe( groupBy( action => action.movieId, action => action, actionsByMovieId$ =>

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

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

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

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

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

    mergeAll() );
  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() );
  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
  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
  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
  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() );
  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)) ) ) );
  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)) ) ) );
  174. @mikeryandev @samjulien DEMO @mikeryandev @samjulien

  175. @mikeryandev @samjulien @mikeryandev @samjulien

  176. @mikeryandev @samjulien @mikeryandev @samjulien

  177. @mikeryandev @samjulien @mikeryandev @samjulien

  178. @mikeryandev @samjulien @mikeryandev @samjulien

  179. @mikeryandev @samjulien UI COMPONENTS DISPATCHER ACTIONS API

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

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

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

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

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