Rx in Angular

Chris Trześniewski

Developer 🥑 Advocate

& Senior Frontend Developer @Scalac

Introduction

  • ... Promise on steroids
  • ... request ==> response ==> done

More than ...

  • ... work with multiple data
  • ... model complex interactions declaratively

It allows to ...

Agenda

  • RxJS - crash course

  • Reactivity in @Components

  • Handling @Outputs with RxJS

  • Reactive @Inputs

    RxJS

    Crash course

    Producer & Observer 

    Button

    Component

    ?

    Observable

    Button

    Component

    Observable

    Observable

    Observable

    Producer <=> Observer

    • source of data arriving over time
    • ie. DOM events, web socket, HTTP request...

    Producer

    • function or object that reacts to data
    • ie. component, event handler ...

    Observer

    Observable

    Producer <=> Observer

    • function that ties Producer and Observer
    • lazy - doesn't evaluate nor attaches listeners unless subscribed
    • need to be unsubscribed

    Observable

    Observable

    example

    const myButton = document.querySelector('#myButton');
    
    const click$ = Rx.Observable.fromEvent(myButton, 'click');
    
    click$.subscribe(event => console.log(event));
    const mouseMove$ = fromEvent(window, 'mousemove');
    
    mouseMove$.subscribe((event: MouseEvent) => {
      console.log(`Mouse: (${event.clientX}, ${event.clientY})`)
    });

    Observable

    example

    const mouseSub = mouseMove$.subscribe((event: MouseEvent) => {
      console.log(`Mouse: (${event.clientX}, ${event.clientY})`)
    });
    
    
    const clicksSub = click$.subscribe(() => {
      mouseSub.unsubscribe();
      clicksSub.unsubscribe();
    });
    

    ! But we must remember to unsubscribe !

    Observable

    operators

    • operators are what gives "super powers" to Observables

    • they allow us to manipulate the stream of data

    • some are similar to what you know from arrays - map, filter, reduce 

    • they can be chained 

    Observable

    operators

    interval(1000).pipe(
      map(x => x * x)
    ).subscribe(x => console.log(x)); // 1 4 9 ..
    interval(1000).pipe(
      filter(x => x % 2 === 0)
    ).subscribe(x => console.log(x)); // 2 4 6 ...
    interval(1000).pipe(
      filter(x => x % 2 === 0),
      map(x => x * x)
    ).subscribe(x => console.log(x)); // 4 16 36 ...

    Tasks

    • create a stream of clicks on a window

    • map it to x coordinate of a click

    • emit only clicks that have x coordinate greater than 100 and lower than 500

    • create an Observable of triple clicks that returns the coordinates of the click in following format: (x, y)  *

    * UIEvent.detail

    Observable

    higher order operators

    • allow us to change / switch to another observable
    • there are different strategies of handling inner observables
    • mergeMap, concatMap, switchMap, exhaustMap

    Observable

    higher order operators

    const myButton = document.querySelector('#myButton');
    
    const click$ = Rx.Observable.fromEvent(myButton, 'click');
    
    const mergeMapped$ = click$.pipe(
      mergeMap(() => interval(1000))
    );
    
    mergeMapped$.subscribe(value => console.log(value));

    Tasks

    • create a timer (counting 1 to 5) and display its value in a div

    • timer, take operators

    • on button click start a timer created above

    • switchMap vs exhaustMap - play with it to see what's a difference

    Reactive components

    Reactive components

    • We can use Observables in component's properties
    • There are two approaches to displaying values from an Observable
    • subscribe vs async pipe

    Reactive components

    .subscribe()

    Then we can just use `componentValue` inside our component's template

    this.ourObservable$.subscribe(value => {
      this.componentValue = value;
    });
    @Component({
      template: `
        <div>{{ componentValue }}</div>
      `
    })

    Reactive components

    .subscribe()

    ! But we must remember to unsubscribe !

    subscription = new Subscription()
    
    ngOnInit() {
      this.subscription.add(
        this.ourObservable$.subscribe(value => {
          this.componentValue = value;
        }); 
      )
    }
    
    ngOnDestroy() {
      this.subsctiption.unsubscribe();
    }

    Reactive components

    .subscribe()

    ! But we must remember to unsubscribe !

    destroy$ = new Subject()
    
    ngOnInit() {
      this.ourObservable$
        .pipe(takeUntil(this.destroy$))
        .subscribe(value => {
          this.componentValue = value;
        });
    }
    
    ngOnDestroy() {
      this.destroy$.next();
      this.destroy$.complete();
    }

    Reactive components

    async pipe

    Then we can use async pipe to unwrap the Observable

    ourObservable$ = interval(1000);
    @Component({
      template: `
        <div>{{ ourObservable$ | async }}</div>
      `
    })

    Reactive components

    .subscribe()

    No need for manual subscription management

    • async pipe “unwraps” the observable and returns a latest value

    • it takes care of unsubscribing by itself when component is destroyed

    Tasks

    • create an Observable which emit a current time (ie. every 500ms)

    • display then current time provided by this Observable using both methods (subscribe & async pipe) 

    Handling events with RxJS

    Events as Observable

    • Events in Angular can be easily transformed into Observables
    • Allows for more control of the application flow

    Events as Observable

    @Component({
      template: `
        <input #myInput>
    
        <div>{{ inputChanges$ | async }}</div>
      `
    })
    inputChanges$: Observable<string>;
    
    @ViewChild('myInput', { static: true }) myInput: ElementRef;
    
    ngOnInit(): void {
      this.inputChanges$ = 
        fromEvent(this.myInput.nativeElement, 'input').pipe(
          map((event: any) => event.target.value)
        );
    }

    Events as Observable

    Subject

    • Special type of Observable which shares single execution among multiple observers (multicasts)
    • It's both an Observable (you can subscribe to it)
    • and an Observer (provides next, error, complete methods)

    Events as Observable

    Subject

    @Component({
      template: `
        <input 
          #myInput
          (change)=“inputValuesSubject.next(myInput.value)"
        >
    
        <div>{{ inputValuesSubject | async }}</div>
      `
    })
    class MyComponent {
      inputValuesSubject = new Subject<string>();
    }

    Events as Observable

    • Now we can use our event streams anyway we want
    • Ie. we could combine to streams to calculate a new value

    Events as Observable

    @Component({
      template: `
        <input
          #firstArgument
          (change)="firstArgSub.next(firstArgument.value)">
        <input
          #secondArgument
          (change)="secondArgSub.next(secondArgument.value)">
    
        <div>Sum {{ sum$ | async }}</div>
      `
    })
    export class MySimpleCalculatorComponent {
      firstArgSub = new Subject<string>();
      firstArgSub = new Subject<string>();
    
      sum$ = combineLatest(
        [this.firstArgumentSubject, this.secondArgumentSubject]
      ).pipe(
        map(([first, second]) => +first + +second)
      );
    }

    Task

    • Create a search input
    • Using Rick and Morty API* load characters filtered by name (max 10)
    • Display found character's names in a list below the search

    Bonus tasks:

    • Prevent calling API to often
    • Add second filter parameter ie. status

    Reactive Inputs

    Reactive Inputs

    • So far we've got reactive properties and events
    • But what about @Inputs?

    Reactive Inputs

    • via ngOnChanges method
    • via property setters

    Getting input changes:

    // the only way to get notified
    // of changes to this prop is `ngOnChanges`
    @Input() firstProperty: string;
    
    @Input() set secondProperty(secondProperty: string) {
      // set some internal properties
      // or do some calculation based on the property
    }

    Reactive Inputs

    • via ngOnChanges method
    • via property setters

    The latter is a good candidate for our reactive inputs

    // we use BehaviorSubject 
    // to always get the current value in new subscribers
    reactiveProperty$ = new BehaviorSubject<string>(null);
    
    @Input() set reactiveProperty(value: string) {
      reactiveProperty$.next(value);
    }

    Reactive Inputs

    • now we can use our reactive property as we did with all Observables before

    But how does it benefit us?

    • all the power of Observables and operators
    • more declarative dependencies

    Reactive Inputs

    reactivePropertyCharacters$ = this.reactiveProperty$.pipe(
      map(value => value.split('')),
      map(characters => characters
        .filter(char => !!char.match(/[a-zA-Z]/))
        .map(char => char.toUpperCase())
      )
    )
    computedProperty$ = combineLatest(
      [propertyA$, propertyB$]
    ).pipe(
      map(([a, b]) => a * b)
    )

    Property dependencies visible right away

    Task

    • inside a component create an input text

    • use value typed inside the filter input to display only the recipes that contain given ingredients

    Bonus tasks:

    • allow for entering multiple ingredients (comma separated)

    • add a checkbox that determines if a recipe should contain all ingredients or just any of them

    • create a component with an array of recipes as an input

    interface Recipe {
      name: string;
      ingredients: string[]
    }

    Thank you!

     

    Any questions?

     

    Rx in Angular - DevMeeting 2020.09.12

    By Chris Trześniewski

    Rx in Angular - DevMeeting 2020.09.12

    • 292