Save 37% off PRO during our Black Friday Sale! »

RxJS in Angular-Anwendungen: so schwimmen Sie nicht mehr gegen den Stream

RxJS in Angular-Anwendungen: so schwimmen Sie nicht mehr gegen den Stream

Sie sind Angular-Entwickler und fühlen sich noch nicht sicher genug, RxJS in vollem Umfang einzusetzen? Es hat sich gezeigt, dass es in der Community noch viel Unsicherheit in der Nutzung von RxJS gibt – selbst für erfahrene Entwickler. In dieser Session mit Yannick Baron wird die Denk- und Arbeitsweise hinter streambasiertem Datenfluss mit RxJS nochmal speziell im Kontext von Angular-Anwendungen angesehen. Darüber hinaus werden häufige Stolperfallen, Antipatterns und Patterns, sowie nützliche Anwendungsbeispiele vorgestellt, um die Anwendung von RxJS einfacher und vertrauter zu machen. Lasst uns gemeinsam mit dem Stream schwimmen.

F5781435bf6dbab3cbbe791111383cf1?s=128

Yannick Baron

October 06, 2020
Tweet

Transcript

  1. RxJS in Angular Anwendungen so schwimmen Sie nicht mehr gegen

    den Strom Yannick Baron @yannick_baron https://www.thinktecture.com/yannick-baron
  2. Intro: Why Streams?

  3. • various forms of asynchronicity in modern web applications •

    WebSockets • DOM Events • AJAX Dealing with Asynchronicity
  4. • designed to handle asynchronicity • start running the moment

    you create it • will always resolve or reject • handles a single event/value
 → can pretty much only cover AJAX • not cancellable Promises
  5. • software design pattern • a Subject notifies a list

    of Observers by calling a provided method • Observers register and unregister with the Subject • useful to be notified of multiple events or actions The observer pattern
  6. • programming paradigm • reacting to asynchronous events
 (e.g. user

    input, data updates, asynchronous responses) • data streams and propagation of change Reactive programming
  7. • library implementing the Observer pattern • API to describe

    data streams • shines when handling multiple reactive events • lots of useful operators
 retrying, error handling, delaying, timing, filtering... → for more than just HTTP requests Rx - Reactive Extensions
  8. • designed to handle asynchronicity • will generally not run

    until there is a subscriber • handles sequence of 0..N asynchronous events over time
 → can handle multiple forms of asynchronicity • can be unsubscribed from (can notify producer) • communicate change or updates to subscribers • combine multiple streams and define how data flows • many operators to help control the flow Rx - On Observables and Streams
  9. Practical Introduction working with RxJS

  10. • Observable
 data stream or source emitting data over time

    • Observer
 function that will be called when new data is emitted • Subscription
 one-sided connection between Observer and Observable • Operators (combination, filtering, transformation, ...)
 replace your observables with a modified one Observable, Observer, Subscription & Operators
  11. new Observable(observer => {
 observer.next('Value 1');
 observer.next('Value 2');
 observer.complete();
 


    return () => {
 // observer unsubscribed
 };
 });
 
 from(['Value 1', 'Value 2']);
 
 of('Value 1');
 
 create, fromEvent, interval, ...
 creating Observables is easy!
 wrap asynchronous actions several (12) creation operators [Examples: 01]
  12. • three events we can react to • next
 receive

    the next value going through the stream • error
 react to the observable throwing - no more values will be emitted
 observers unsubscribe - observable does not complete • complete
 observable completes without error - no more values will be emitted
 observers unsubscribe
 next, error, complete [Examples: 02, 03]
  13. • Observable
 data stream or source emitting data over time


    is cold / unicast: start upon subscription with new execution context
 → single listener (subscriber) • Subject (AsyncSubject / BehaviorSubject / ReplaySubject)
 is an Observable that we can tell when to next / error / complete
 hot / multicast: run without subscription and keep execution context
 → multiple listeners (subscriber) Subject & Observable - Hot & Cold [Examples: 04, 05]
  14. • Subject
 emits values regularly - you can miss values

    • AsyncSubject
 only emits the last value once the subject completes • BehaviorSubject (stateful)
 emits default value or last emitted value upon subscription, then regularly • ReplaySubject
 emits the last N values upon subscription, then regularly Subjects [Examples: 05, 06, 07, 08]
  15. • Operators (combination, filtering, transformation, ...)
 replace your observables with

    a modified one • https://rxmarbles.com/ Operators and marble diagrams Observable Events over time Operator Observable Resulting Observable
  16. Unsubscribing and Completion Always unsubscribe?

  17. • notifies Subject to remove Observer • Observable can perform

    cleanup logic
 e.g. cancelling an http request, unsetting timers, closing connections, ... • will stop Subscription from executing
 if subscription is kept alive on a destroyed Component, it will still execute
 can cause memory leaks • possibility to unsubscribe via operators • Subject completion causes the Observers to unsubscribe Unsubscribing [Examples: leak]
  18. • No... Well... Not directly... • ... if you know

    exactly what you are doing. • Always unsubscribing is the safest way to prevent leaks or side-effects
 good first step
 more comfortable with RxJS → easily identify when to unsubscribe • Unsubscribe cleverly. At the right moment. In the most elegant way.
 reason about it - make your app behave the way you need it to
 make use of operators
 (In Angular: async pipe unsubscribes for you!)
 Do we always have to unsubscribe?
  19. Use cases and more complex examples

  20. Antipatterns & Patterns

  21. this.route.params.pipe(
 switchMap(({ customerId }) => customerService.getCustomer(customerId)),
 ).subscribe(customer => {
 this.customer

    = customer;
 
 addressService.getAddress(customer.addressId)
 .subscribe(address => this.address = address);
 
 ordersService.getOrders(customer.orderIds)
 .subscribe(orders => this.orders = orders);
 }); Antipattern: Nested Subscriptions
  22. // Emitting customerIds 1 and 2 with a 25ms delay

    createStream<number>([1, 2], 25) .subscribe(id => { // Update the view with the received customer object updateView('customer', `Customer #${id}`) // Request address for customer but delay address for Customer #1 by 1000ms requestAddress(id) .subscribe(address => updateView('address', address)); }); Antipattern: Nested Subscriptions
  23. • our user navigates to the detail page of Customer1

    • request Customer1 data object • retrieve Customer1 and update view • request address data for Customer1 ... taking a while due to the API choking • the impatient user of our application navigates to detail page of Customer2 • request Customer2 data object • retrieve Customer2 and update view • request address data for Customer2 • retrieve address data for Customer2 and update view • retrieve address data for Customer1 and update view ... finally resolved! Antipattern: Nested Subscriptions https://rxjs-v6juyt.stackblitz.io
  24. // Emitting customerIds 1 and 2 with a 25ms delay

    createStream<number>([1, 2], 25) // Switch stream to the address request .pipe(switchMap(id => { // Received customer object const customer = `Customer #${id}`; return requestAddress(id) // Combine the customer object and the resolved address to a single object .pipe(map(address => ({ customer, address }))); })) .subscribe(({ customer, address }) => { // Update the view with the matching information updateView('customer', customer); updateView('address', address); }); Antipattern: Nested Subscriptions https://rxjs-aqinqu.stackblitz.io
  25. this.route.params.pipe( switchMap(({ customerId }) => customerService.getCustomer(customerId)), tap((customer) => { this.customer

    = customer; this.code = makeCode(customer); }), switchMap(() => myService.retrieveByCode(this.code)), tap((result) => { this.result = result; }), switchMap(() => otherService.byCustomerAndResult(this.customer, this.result)), ).subscribe(combinedResult => { this.result = combinedResult; this.view = moreComplexComputation(this.customer, this.code, combinedResult); }); Antipattern: Stateful Streams
  26. // Emitting customerIds 1 and 2 with a 25ms delay

    createStream<number>([1, 2], 25) .pipe( // Switch to request customer object switchMap(id => requestCustomer(id)), // Switch to request made by generating the customer code switchMap((customer) => { const code = makeCode(customer); return requestByCode(code) // Combine results into a bundle .pipe(map(result => ({ customer, code, result }))); }), // Switch to final request switchMap(({ customer, result, ...bundle }) => { return requestByCustomerAndResult(customer, result) // Add the new result to the bundle .pipe(map(combinedResult => ({ ...bundle, customer, result, combinedResult }))); }), ) .subscribe(({ customer, code, result, combinedResult }) => { // Update view or properties all at once updateView('customer', customer.name); updateView('code', code); updateView('result', result.result); updateView('combined', combinedResult); }); Antipattern: Stateful Streams
  27. // Stream that emits an user object when the userId

    route param changes const user$ = this.route.params.pipe( switchMap(({ userId }) => requestUser(userId)), ); // Stream that emits a list of all role objects once const roles$ = requestRoles(); // Combine both streams const userAndRoles$ = combineLatest(user$, roles$); // Transform combined result into UserView model this.userView$ = userAndRoles$.pipe( // Create a UserView object containing the user's properties and its roles map(([user, roles]) => ({ ...user, roles: resolveUserRoles(user, roles) })) ); Pattern: Single Result Data Stream and async pipe
  28. <dl *ngIf="userView$ | async as userView"> ... </dl> Pattern: Single

    Result Data Stream and async pipe https://angular-ci9trw.stackblitz.io
  29. Getting Help learning to help yourself

  30. • https://www.learnrxjs.io/
 explains all operators with examples and more •

    https://rxmarbles.com/
 visual representation of some operators • https://stackblitz.com/
 quickly construct a demo for your use case • https://rxjs-dev.firebaseapp.com/operator-decision-tree
 helps you find the right operator, good in the beginning Great resources
  31. It's a wrap! https://www.thinktecture.com/yannick-baron