Slide 1

Slide 1 text

RxJS in Angular Anwendungen so schwimmen Sie nicht mehr gegen den Strom Yannick Baron @yannick_baron https://www.thinktecture.com/yannick-baron

Slide 2

Slide 2 text

Intro: Why Streams?

Slide 3

Slide 3 text

• various forms of asynchronicity in modern web applications • WebSockets • DOM Events • AJAX Dealing with Asynchronicity

Slide 4

Slide 4 text

• 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

Slide 5

Slide 5 text

• 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

Slide 6

Slide 6 text

• programming paradigm • reacting to asynchronous events
 (e.g. user input, data updates, asynchronous responses) • data streams and propagation of change Reactive programming

Slide 7

Slide 7 text

• 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

Slide 8

Slide 8 text

• 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

Slide 9

Slide 9 text

Practical Introduction working with RxJS

Slide 10

Slide 10 text

• 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

Slide 11

Slide 11 text

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]

Slide 12

Slide 12 text

• 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]

Slide 13

Slide 13 text

• 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]

Slide 14

Slide 14 text

• 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]

Slide 15

Slide 15 text

• 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

Slide 16

Slide 16 text

Unsubscribing and Completion Always unsubscribe?

Slide 17

Slide 17 text

• 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]

Slide 18

Slide 18 text

• 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?

Slide 19

Slide 19 text

Use cases and more complex examples

Slide 20

Slide 20 text

Antipatterns & Patterns

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

// Emitting customerIds 1 and 2 with a 25ms delay createStream([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

Slide 23

Slide 23 text

• 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

Slide 24

Slide 24 text

// Emitting customerIds 1 and 2 with a 25ms delay createStream([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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

// Emitting customerIds 1 and 2 with a 25ms delay createStream([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

Slide 27

Slide 27 text

// 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

Slide 28

Slide 28 text

...
Pattern: Single Result Data Stream and async pipe https://angular-ci9trw.stackblitz.io

Slide 29

Slide 29 text

Getting Help learning to help yourself

Slide 30

Slide 30 text

• 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

Slide 31

Slide 31 text

It's a wrap! https://www.thinktecture.com/yannick-baron