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
Image credits:
https://www.dofactory.com/javascript/observer-design-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
excersise file: https://stackblitz.com/edit/adv-rxjs-1
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 duringsubscription - 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
excersise file: https://stackblitz.com/edit/adv-rxjs-2
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))
excersise file: https://stackblitz.com/edit/adv-rxjs-3
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
})
})
excersise file: https://stackblitz.com/edit/rxjs-adv-4
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
excersise file: https://stackblitz.com/edit/rxjs-adv-5
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
excersise file: https://stackblitz.com/edit/rxjs-adv-6
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