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

RxJS in Angular-Anwendungen: so schwimmen Sie n...

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.

Yannick Baron

October 06, 2020
Tweet

More Decks by Yannick Baron

Other Decks in Programming

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. • various forms of asynchronicity in modern web applications •

    WebSockets • DOM Events • AJAX Dealing with Asynchronicity
  3. • 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
  4. • 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
  5. • programming paradigm • reacting to asynchronous events
 (e.g. user

    input, data updates, asynchronous responses) • data streams and propagation of change Reactive programming
  6. • 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
  7. • 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
  8. • 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
  9. 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]
  10. • 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]
  11. • 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]
  12. • 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]
  13. • 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
  14. • 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]
  15. • 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?
  16. 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
  17. // 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
  18. • 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
  19. // 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
  20. 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
  21. // 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
  22. // 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
  23. <dl *ngIf="userView$ | async as userView"> ... </dl> Pattern: Single

    Result Data Stream and async pipe https://angular-ci9trw.stackblitz.io
  24. • 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