Observables Are Not Promises!
Elanna Grossman - Valley DevFest 2018
Introduction
2
Elanna Grossman
3
Email: | elanna.grossman@gmail.com |
GitHub: | https://github.com/karvel |
App: | https://github.com/Karvel/angular-observable-examples-app |
Website: | hapax-legomenon.net |
RxJS
Asynchronous library implementation for Javascript. Subset of ReactiveX. Best known for its Observable structures.
4
Angular
Popular modern front-end framework. Built Observables into the architecture of the framework.
5
Firebase
Backend as a Service that provides a real time database. Essentially observable streams on the backend.
6
Relevant Technology
RxJS - operator imports changed in v6, pipeable operators only available since v5.5
Angular - HttpClient introduced in v4.3
7
Technology | Version |
RxJS | 6.0 |
Angular | 6.1 |
Firebase | 5.5 |
AngularFire2 | 5.0 |
Assumptions
8
Promises Vs. Observables
9
Promise Structure
Eager
Executes its logic as soon as it is invoked.
Single Value
Promises have three states: pending, fulfilled, rejected. Promises return either one resolved value (success), or reason for rejection (failure).
Chaining promises returns a new promise that executes its own callback.
10
Observable Structure
Lazy
Observables do not execute until activated (subscribed to). This is like how functions work: declaring a function without calling it doesn’t execute its internal logic. Observables by default also execute their logic for every Subscription.
11
Observable Structure
Single Or Multiple Values
Can emit once, multiple times, or indefinitely.
Create
Observables can be produced locally using one of the Observable creation methods. These can take primitives or objects as arguments, or even Observables.
12
Observable Structure
Has three notification states : next, complete, and error.
Next
AKA emitting, this is when new values come through the stream.
Complete
This marks the successful end of stream.
Error
Throws an error or exception, ends the stream.
13
Observable Structure
Operate
Operators allow for complex manipulation of stream output. Each operator creates a new Observable and works on data returned from the previous operator. Operators can also trigger the complete state of an Observable.
Pipe
RxJS 6 introduced piping instead of chaining. They made this change for performance reasons.
14
Observable Structure
Marble Diagrams
Visual representations of how streams behave over time. Helpful in understanding operators.
15
16
Subscription Structure
Provide
Subscriptions cause the stream logic to execute and provide the results to an observer.
Dispose
Subscriptions also can terminate the execution of Observables. It’s important to have something outside of Observables that does this because an Observable might emit indefinitely.
17
Observer Structure
Consume
Observers consume streams provided to them by Subscriptions. Each Subscription creates a new observer.
Callback
Observers have three callbacks, matching the Observable notification states above: next, complete, and error.
18
Terminology Review
19
Producer
What generates the stream and its possible states.
e.g. Observables and Subjects
this.fooService.getFooList()
.pipe(map(result => result.foos))
.subscribe(response => { this.foo = response; });
20
Operator
Interacts with the stream output.
e.g. map, take, catchError
this.fooService.getFooList()
.pipe(map(result => result.foos))
.subscribe(response => { this.foo = response; });
21
Provider
An instance of a disposable resource that is given to the consumer.
e.g. Subscriptions
this.fooService.getFooList()
.pipe(map(result => result.foos))
.subscribe(response => { this.foo = response; });
22
Consumer
The callbacks of the producer notification states provided by the Subscription.
e.g. Observers and Subjects
this.fooService.getFooList()
.pipe(map(result => result.foos))
.subscribe(response => { this.foo = response; });
23
Stream
Streams are sequences of ongoing events.
Observables and Subjects are streams.
24
Observable | Producer of stream |
Subscription | Provider of stream |
Observer | Consumer of stream |
Subject | Producer and consumer of stream |
25
Hot Vs Cold Observables
26
Cold Observables
Each Subscription to a cold Observable creates a new instance, and begins emitting from the start of the stream.
HttpClient returns cold observables by default.
27
Hot Observables
New Subscriptions subscribe to the same source rather than getting new instances.
New Subscriptions will only get newly emitted values, not anything they missed.
28
Hot vs Cold Observable Analogy
Cold Observables are like Netflix. Hot Observables are like a cinema screening.
Netflix (Cold)
Cinema Screening (Hot)
29
How To Make Cold Observables Hot
Using .share() or .shareReplay() operators covert cold observables to hot ones.
Cold
public getFooList(): Observable<Foo[]> {
return this.http.get(`${this.url}/foo/`).pipe(
map((response: Foo[]) => response),
);
}
Hot
public getFooList(): Observable<Foo[]> {
return this.http.get(`${this.url}/foo/`).pipe(
map((response: Foo[]) => response),
shareReplay(1),
);
}
30
When To Use Cold Observables
When creating the source of the observable - of(), from() - Observable creation methods create cold Observables.
public getBarList(): Observable<string[]> {
const barList: string[] = [ 'foo', 'bar', 'baz' ];
return of(barList);
}
31
Higher Order Observables
Higher Order Observables are Observables that take Observables as arguments. They are used when multiple streams need to interact.
32
forkJoin
Observable creation method - cannot exist inside a pipe.
Preserves order - closest to Promise.all()
Waits until all streams are complete and emits last value from each.
33
combineLatest
Observable creation method - cannot exist inside a pipe.
Provides most recent values from all streams once all have emitted at least once.
34
Higher Order Observable Mapping Operators
These operators map the value of an outer observable to an inner observable. These can replace nested subscriptions.
35
concatMap
Use if need data from each stream sequentially before the next outer stream is mapped.
36
mergeMap
Use if the order of the input streams and the output is not important.
37
switchMap
Use if need most recent data from outer stream. Will cancel inner stream and replace.
Use case: Autocomplete
38
exhaustMap
Use if need to wait for inner stream to complete before mapping next outer stream.
Use case: Saving
39
Pitfalls
40
41
Observables Must Be Consumed
Observables are lazy and do not execute their logic until subscribed to.
Bad
this.fooService.getFoo();
Good
this.fooService.getFoo().subscribe(response => { this.foo = response; });
42
Nesting Subscriptions
Nesting Subscriptions are often a result of not taking a top-down approach in designing data flow.
43
Not Managing Subscriptions
Subscriptions are in charge of determining when to destroy a connection to a stream. Some Observables are designed to emit indefinitely and so this is an important form of housekeeping.
When To Unsubscribe
How To Unsubscribe
Angular handles some Subscriptions automatically.
44
Angular Implementation of RxJS
45
Async Pipe
Only available in the template. Automatically subscribes to a stream when ready and unsubscribes when the view is destroyed.
Each instance of async pipe is like a new Subscription.
Works best with *ngIf ... as" syntax.
46
HttpClient
HttpClient creates Observables that complete once they have emitted one value.
Subscriptions to HttpClient Observables do not need to unsubscribe.
47
Router
Any Observable logic provided to the Router must complete - that is the signal Angular uses under the hood to subscribe. Subscriptions to Router Observables do not need to unsubscribe.
E.g. Resolvers, Guards, interacting with Router or ActivatedRoute.
Unsubscribing from these does no harm aside from the extra code and might be a good idea while getting into the habit.
48
Error Handling
Complete and Error notifications both end streams. If a stream completes, it cannot emit again or error out. If a stream errors out, it also cannot emit again and will never complete.
49
Subscription Error Handler
Outputs error from Observable.
this.fooService.getFooList()
.subscribe(
(response: Foo[]) => { this.foo = response; },
(error: FooError) => { this.errorMessage = error; },
);
50
Observable Error Handler - catchError
Works like other operators, takes input Observable value, returns new Observable.
Errored out stream is now terminated, but we can provide a replacement fallback value or rethrow the error after handling it.
51
Retry
Can subscribe again to an Observable that ended in an error state to attempt to complete.
Different strategies in when to retry after an error.
52
Advanced Structures
53
Subjects
Subjects are both Observables and Observers. They are also multicast, so multiple Subscriptions get the same execution for provided values. The `shareReplay()` operator we discussed above returns a Subject, which is how it turns a cold Observable hot. Subjects can directly call next(), error(), and complete(). There are multiple kinds of Subjects!
54
Schedulers
Schedulers handle the timing of sending Observable notifications, and when subscribe takes effect. Schedulers are automatically implemented on the default operators that deal with timing (concurrency). You can adjust this behavior.
55
Conclusion
56
Conclusion
57
Elanna Grossman
58
Email: | elanna.grossman@gmail.com |
GitHub: | https://github.com/karvel |
App: | https://github.com/Karvel/angular-observable-examples-app |
Website: | hapax-legomenon.net |