Advanced Observable Patterns in Angular apps with RxJS

Contents

Part 1 - Reactive programming and RxJS

  • Creating observables
  • Operators
  • Higher order observables
  • Error-handling

 

Part 2 - Observables in Angular

  • EventEmitters
  • Http
  • Forms
  • Router

INTRODUCTION

Reactive Programming

// concert spectators
let inMainStand = 300;
let inVipStand = 50;

// code...

let total = inMainStand + inVipStand; // 350

// code...

inMainStand += 60;

console.log(total); // total = ?
total = inMainStand + inVipStand; // 410
  • imperatively recalculate the value of total

Reactive Programming

Reactive Programming

Product

Vendor

Cart

Cupon

Sale

Payment

User profile

Invoice

Reactive Programming

Cart

Invoice

Add product...

...update the total

Reactive Programming

Cart

Invoice

import {Invoice} from './invoice';

class Cart {
    
    constructor(invoice: Invoice){ ... }
    
    addProduct(product) {
        ...
        this.invoice.update(product); // needs to be public
        ...
    }
}

Passive

Proactive

Reactive Programming

Cart

Invoice

import {Cart} from './cart';

class Invoice {
    
    constructor(cart: Cart){ ... }
    
    init() {
        ...
        this.cart.onCartChange( callback )

    }
}    

Reactive

Listenable

Reactive Programming

Cart

Cupon

Sale

Payment

Invoice

  • What does it affect? - look inside
  • How does it work? - find usages

Passive programming

  • remote setters and updates

Reactive Programming

Cart

Cupon

Sale

Payment

Invoice

  • What does it affect? - find usages of events
  • How does it work? - look inside

Reactive programming

  • events, observation and self updates

Reactive Programming

Interruptions in web applications

  • API events
  • WebSockets
  • User-triggered events
  • Timers (setTimeout, setInterval)

Reactive Programming

(Global) Event Bus

Reactive Programming

Observer pattern

Reactive Programming

Observer vs PubSub

Reactive Programming

Managing side-effects

SYNC

ASYNC

SINGLE

MULTIPLE

function

Reactive Programming

Managing side-effects

SYNC

ASYNC

SINGLE

MULTIPLE

function

generators

Reactive Programming

Managing side-effects

SYNC

ASYNC

SINGLE

MULTIPLE

function

promises

generators

Reactive Programming

Managing side-effects

SYNC

ASYNC

SINGLE

MULTIPLE

function

promises

generators

observables

OBSERVABLES

Special credits

Michael Hladky
@Michael_Hladky

 

- marble diagrams

- inspirational talks

Ben Lesh
@BenLesh

 

inspirational talks -

 work on RxJS -

OBSERVABLES

  • A stream of data ( 0 or more values of any type)

 

  • Pushed over any amount of time (can end, but not necessarily)

 

  • Cancelable ( can be stopped from emitting a value - unsubscribed)

 

  • Lazy - won't emit values until we subscribe to them

 

  • Just a function that takes an observer and returns a function

OBSERVABLES

Dealing with async in our apps:

  • Dom events
  • Animations
  • AJAX
  • WebSockets
  • Server Events

 

AJAX with callbacks

getData(function(successResult) {
    // do something with the data
});
getData(
  function(successResult) {
    // do something with the data
  },
  function(faliureError) {
    // handle the error
  },
);

AJAX with callbacks

let result = fetch('api/users.json');

result
  .then(success => {
    // handle success
  })
  .catch(error => {
    // handle error
  })

AJAX with promises

result
  .then(...)
  .then(...)
  .then(...)

Observables

Unifying callbacks, promises and event handlers

Shape of an observable:

  • A function
  • accepts an observer ( an object with `next`, `error` and `complete` methods on it)
  • returns a cancellation function

 

Creating an Observable

let observable$ = new Observable(() => {

})

Observable

  • read-only - consumer
  • plain function
  • exposes 3 channels: next, error and complete

Creating an Observable

let observable$ = new Observable(observer => {
  observer.next(1);
  observer.next(2);
})

Observer

  • write-only - producer
  • instance passed to observable
  • provide next, error and complete methods
  • just an interface

Creating an Observable

let observable$ = new Observable(observer => {
  observer.next(1);
  observer.next(2);
})

observable$.subscribe(value => {
  console.log(value)
}

Subscription

  • triggers the observable execution
  • returns an unsubscribe() method that stops the observable

Creating an Observable

let observable$ = new Observable(observer => {
  observer.next(1);
  observer.next(2);

  return () => {
    // cleanup resources when done
  };
})

const subscription = observable$.subscribe(value => {
  console.log(value)
})

subscription.unsubscribe(); // stop emitting values

Creating an Observable

let observable$ = new Observable(observer => {
  observer.next(1);
  observer.next(2);

  observer.error(new Error('Bad'));
})

const subscription = observable$.subscribe(
  value => { console.log(value)},
  error => { console.log(error.message)}
)

Creating an Observable

Creation functions

  • of(value1, value2, value3)

  • from(promise/itterable/observable)

  • fromEvent(target, eventName)

  • interval(time)

  • timer(time)

Creating a promise

let promise = new Promise((resolve, reject) => {

  doAsyncThing((err, value) => {
    if (err) {
      reject(err);
    } else {
      resolve(value)
    }
  })
})

promise.then(successFn, errorFn);

Creating an observable

let promise = new Promise((resolve, reject) => {

  doAsyncThing((err, value) => {
    if (err) {
      reject(err);
    } else {
      resolve(value)
    }
  })
})

promise.then(successFn, errorFn);

Hot vs Cold observables

COLD is when your observable creates the producer

// COLD
var cold = new Observable((observer) => {
  var producer = new Producer();
  // have observer listen to producer here
});
  • producer is created and activated during subscription
  • unicast => everyone gets their own instance
  • observables are "cold" by default

Hot vs Cold observables

  • HOT is when your observable closes over the produce

// HOT
var producer = new Producer();
var cold = new Observable((observer) => {
  // have observer listen to producer here
});
  • producer is created and activated outside and independent of subscription
  • multicast => shared reference to the producer

Subjects

Observables are unicast - each subscriber manages its own execution context

Subjects are multicast observables - values are multicasted to many Observers

Types of subjects

  • BehaviorSubject - has the notion of "current value" 
  • ReplaySubject - can record part of it's execution

Operators

Observables are collections of pushed values or events that we can:

  • query (filter)
  • transform (map)
  • accumulate (reduce)
  • join
  • flatten
  • more...

Array functions

let array = [1,2,3,4,5];

array
  .map(v => v * v)
  .filter(v => v > 5 && v < 20)
  .reduce((acc, curr) => acc + curr, 0)

// 25

Array functions

Operators

let observable$ = from([1,2,3,4,5]);

observable$
  .pipe(
    map(v => v * v),
    filter(v => v > 5 && v < 20),
    reduce((acc, curr) => acc + curr, 0)
  )
  .subscribe(val => console.log(val))

// 25

map

filter

scan

reduce

tap

Custom operators

const toUpperCase = 
  (source: Observable<any>) => {
    return new Observable((observer) => {
      return source.subscribe({
        next(x) {
          observer.next( x.toUpperCase() )
        }
      })
    }
  )}

from('hello')
  .pipe(toUpperCase)
  .subscribe(val => l(val))

Custom operators

const pow = (p = 1) =>
  (source: Observable<any>) => {
    return new Observable((observer) => {
      return source.subscribe({
        next(val) {
          observer.next( Math.pow(val, p) )
        }
      })
    }
  )}

from([1, 2, 3])
  .pipe(pow(3))
  .subscribe(val => l(val))

Operators can take parameters as well

Stopping streams

Higher order observbles

const buttonObs$ = fromEvent(querySelector('button'), 'click');

buttonObs$.subscribe(() => {
  http$.get('/api/users').subscribe( data => {
    // handle loaded data
  })
})

Flatning operators

mergeMap

Flatning operators

concatMap

Flatning operators

switchMap

Flatning operators

exhaustMap

Combination operators

zip

Combination operators

combineLatest

Combination operators

forkJoin

Combination operators

withLatestFrom

Error Handling

const obs$ = new Observable(observer => {
  observer.closed;    // false 
  observer.next(1);   // emit 1
  observer.complete();
  observer.closed;    // true
  observer.next(2);   // won't emit
});
  • Completing a stream

Error Handling

  • A stream can only error once
const obs$ = new Observable(observer => {
  observer.closed;    // false 
  observer.next(1);   // emit 1
  observer.error(new Error('Bad!'));
  observer.closed;    // true
  observer.next(2);   // won't emit
});

obs$
  .subscribe({
    next: v => console.log(v),
    error: e => console.log(e.message)
  });

Error Handling

  • A stream can only error once
const obs$ = new Observable(observer => {
  observer.next(1);
  observer.error(new Error('BANG!'));
}).pipe(
  catchError(err => {
    console.log('Intercepted error:' + err);
    return of('I got this');
  })
)

obs$.subscribe(v => console.log(v));

// 1   'I got this'

Error Handling

  • Recovering after an error - the retry operator
const getData$ = http.get('/api/users')
    .pipe(
        retry(3),
        catchError(() => of('Something went wrong');
    )

getData$.subscribe(value => console.log(value));

Schedulers

Schedulers control timing around when an event occurs

 

RxJS Schedulers

  • queue - During same "job", but on a queue
  • asap - Next "job", aka, "microtask"
  • async - Next "timeout"
  • animationFrame - Next requestAnimationFrame

RxJS in Angular

Observables in Angular

Angular power-up -> async pipe

Advanced Observable Patterns in Angular apps with RxJS

By Andrei Antal

Advanced Observable Patterns in Angular apps with RxJS

  • 2,058