$30 off During Our Annual Pro Sale. View Details »

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.

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

    View Slide

  2. Intro: Why Streams?

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  6. • programming paradigm
    • reacting to asynchronous events

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

    View Slide

  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

    View Slide

  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

    View Slide

  9. Practical Introduction
    working with RxJS

    View Slide

  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

    View Slide

  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]

    View Slide

  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]

    View Slide

  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]

    View Slide

  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]

    View Slide

  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

    View Slide

  16. Unsubscribing and Completion
    Always unsubscribe?

    View Slide

  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]

    View Slide

  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?

    View Slide

  19. Use cases
    and more complex examples

    View Slide

  20. Antipatterns & Patterns

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide


  28. ...

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

    View Slide

  29. Getting Help
    learning to help yourself

    View Slide

  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

    View Slide

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

    View Slide