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

GroupBy: RxJS’ Secret Weapon for Complex UIs

Sam Julien
December 01, 2020

GroupBy: RxJS’ Secret Weapon for Complex UIs

What if we told you that one of the most useful operators in RxJS is 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. You will see how to use groupBy to give users an excellent experience, handling race conditions and back pressure with ease. You will also learn the *why* behind when to use groupBy and common pitfalls to avoid with it. Of course, you'll also leave with thorough sample code you can take back to work and start building from right away.

Sam Julien

December 01, 2020
Tweet

More Decks by Sam Julien

Other Decks in Programming

Transcript

  1. @mikeryandev @samjulien HOW DO WE MAP THE BUTTON CLICKS TO

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

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

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

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

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

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

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

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

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

    }) => setButtonEmoji(movieId)), switchMap(movie => toggleStatus(movie.movieId)) );
  11. [[, , ], [, , ], [, , ]] array.flat()

    [, , , , , , , , ] @mikeryandev @samjulien
  12. of(of(, , ), of(, , ), of(, , )) observable$.pipe(mergeAll())

    of(, , , , , , , , ) @mikeryandev @samjulien
  13. actions$.pipe( groupBy(action => action.movieId), map(actionsByMovieId$ => { return actionsByMovieId$.pipe( switchMap(action

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

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

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

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

    => { return toggleStatus(action.movieId); }) ); }), mergeAll() ); @mikeryandev @samjulien
  18. groupBy( /* Key Selector */, /* Element Selector */, /*

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

    group when it noti f i es or completes. DURATION SELECTOR @mikeryandev @samjulien
  20. groupBy( /* Key Selector */, /* Element Selector */, /*

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

    discard group after 15s of silence */, ) @mikeryandev @samjulien
  22. @mikeryandev @samjulien Returns an observable that will clean up the

    group when it noti f i es or completes. DURATION SELECTOR @mikeryandev @samjulien
  23. @mikeryandev @samjulien Returns an observable that will clean up the

    group when it noti f i es or completes. DURATION SELECTOR @mikeryandev @samjulien
  24. actions$.pipe( groupBy( action => action.movieId, action => action, actionsByMovieId$ =>

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

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

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

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

    actionsByMovieId$.pipe( timeoutWith(15000, EMPTY), ignoreElements() ) ) ); @mikeryandev @samjulien
  29. 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() );
  30. actions$.pipe( groupBy( action => action.movieId, action => action, actionsByMovieId$ =>

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

    actionsByMovieId$.pipe( timeoutWith(15000, EMPTY), ignoreElements() ) ), map(actionsByMovieId$ => actionsByMovieId$.pipe(
  32. 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() );
  33. 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
  34. 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
  35. 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
  36. 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() );
  37. actions$.pipe( groupBy( action => action.movieId, action => action, actionsByMovieId$ =>

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

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