RxJS Mastery

Что мы тут делать собрались?

Agenda

  • What is RxJS
  • Major Entities
  • Creational Operators
  • Custom Operators
  • Mapping
  • Filtering
  • Rate Limiting
  • Transformation
  • Error Handling
  • Combination
  • Multicasting
  • Schedulers
  • Hidden RxJS Operators

Practice

What is RxJS

Push vs Pull

Why RxJS Important

Major Entities:

  • Observables
  • Observers
  • Subscriptions
  • Subscribers
  • Subjects

Observables

Observers

Subscriptions

Subscribers

Subjects

How to create

Observable?

new Observable((observer: Observer<number>) => {
  
  observer.next(0);
  
  observer.complete();
  
  observer.error(new Error('WTF?'));
});

Task 1

Create an Observable that emits 10 random values then completes and may accidentially throw an error.

const subscription: Subscription = new Observable(...);

subscription.unsubscribe();

Creational Operators

of(10, 20, 30); // 10, 20, 30

from([1,2,3,4,5]); // 1,2,3,4,5

interval(1000); // 0,1,2,3,4,5

timer(1000); // emits 0 after 1 second

fromEvent(button, 'click');

Task 2

Create a creational operator that allows subscribing to events on the DOM element. Please, make sure it's possible to unsubscribe from events. You are not allowed to use rxjs except Observable constructor.

How to create custom operators?

function myNewOperator<T, R>(source: Observable<T>): Observable<R> {
  return new Observable((observer: Observer) => {
    const subscription = source.subscribe({
      next: (x) => observer.next(x),
      complete: (e) => observer.complete(e),
      error: e => observer.error(e),
    });
    
    return subscription;
  });
}
function myNewOperator<T, R>(source: Observable<T>): Observable<R> {
  ...
}
  
from([1,2,3,4,5])
  .pipe(
    myNewOperator,
  )
  .subscribe();
function myNewOperator<T, R>(source: Observable<T>): Observable<R> {
  return new Observable((observer: Observer) => {
    const subscription = source.subscribe({
      next: (x) => observer.next(x),
      complete: (e) => observer.complete(e),
      error: e => observer.error(e),
    });
    
    return subscription;
  });
}
function myNewOperator<T, R>(source: Observable<T>): Observable<R> {
  return new Observable((observer: Observer) => {
    return source.subscribe({
      next: (x) => observer.next(x),
      complete: (e) => observer.complete(e),
      error: e => observer.error(e),
    });
  });
}
function myNewOperator<T, R>(source: Observable<T>): Observable<R> {
  return new Observable((observer: Observer) => {
    return source.subscribe(observer);
  });
}

Filtering Operators

take();

take();

interval(1000)
  .pipe(
    take(5),
  )
  .subscribe(() => {});

take();

stream$
  .pipe(
    take(1),
  )
  .subscribe(() => {});

take();

fromEvent(document, 'click')
  .pipe(
    take(1),
  )
  .subscribe(() => {});

take();

this.store
  .pipe(
  	select('count'),
    take(1),
  )
  .subscribe(() => {});

takeUntil();

takeUntil();

const timer$ = timer(5000);

interval(1000)
  .pipe(
    takeUntil(timer$),
  )
  .subscribe(() => {});

takeUntil();

class Component implements OnInit, OnDestroy {
  
  private destroy$ = new Subject();
  
  ngOnInit(): void {
    stream$
      .pipe(
        takeUntil(this.destroy$),
      )
      .subscribe(() => {});
  }
  
  ngOnDestroy(): void {
    this.destroy$.next();
  }
}

takeUntil();

mousedown$
  .pipe(
    mergeMap(_ => {
      return mousemove$.pipe(
        // Do Something here
        map(),
       
        // Do until mouseup
        takeUntil(mouseup$)
      );
    })
  )
  .subscribe();

takeWhile();

skip();

skipUntil();

skipWhile();

Transformation Operators

switchMap();

switchMap(); - countdown

import { interval, fromEvent, merge, empty } from 'rxjs';
import { switchMap, scan, takeWhile, startWith, mapTo } from 'rxjs/operators';

const COUNTDOWN_SECONDS = 10;

// elem refs
const remainingLabel = document.getElementById('remaining');
const pauseButton = document.getElementById('pause');
const resumeButton = document.getElementById('resume');

// streams
const interval$ = interval(1000).pipe(mapTo(-1));
const pause$ = fromEvent(pauseButton, 'click').pipe(mapTo(false));
const resume$ = fromEvent(resumeButton, 'click').pipe(mapTo(true));

const timer$ = merge(pause$, resume$)
  .pipe(
    startWith(true),
    switchMap(val => (val ? interval$ : empty())),
    scan((acc, curr) => (curr ? curr + acc : acc), COUNTDOWN_SECONDS),
    takeWhile(v => v >= 0)
  )
  .subscribe((val: any) => (remainingLabel.innerHTML = val));

concatMap();

mergeMap();

exhaustMap();

What the difference?

Made with Slides.com