1 of 58

Observables Are Not Promises!

Elanna Grossman - Valley DevFest 2018

2 of 58

Introduction

2

3 of 58

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

4 of 58

RxJS

Asynchronous library implementation for Javascript. Subset of ReactiveX. Best known for its Observable structures.

4

5 of 58

Angular

Popular modern front-end framework. Built Observables into the architecture of the framework.

5

6 of 58

Firebase

Backend as a Service that provides a real time database. Essentially observable streams on the backend.

6

7 of 58

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

8 of 58

Assumptions

  • Familiarity with Angular. Many examples in this talk are Angular-specific.
  • Familiarity with promises.
  • Exposure to RxJS.
  • Using Firebase for its real time database functionality, not an expert.

8

9 of 58

Promises Vs. Observables

9

10 of 58

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

11 of 58

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

12 of 58

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

13 of 58

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

14 of 58

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

15 of 58

Observable Structure

Marble Diagrams

Visual representations of how streams behave over time. Helpful in understanding operators.

15

16 of 58

16

17 of 58

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

18 of 58

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

19 of 58

Terminology Review

19

20 of 58

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

21 of 58

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

22 of 58

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

23 of 58

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

24 of 58

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 of 58

25

26 of 58

Hot Vs Cold Observables

26

27 of 58

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

28 of 58

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

29 of 58

Hot vs Cold Observable Analogy

Cold Observables are like Netflix. Hot Observables are like a cinema screening.

Netflix (Cold)

  • Each person watches their own copy of the movie.
  • The movie starts from the beginning for each person.
  • If nobody selects that movie, it doesn’t play.

Cinema Screening (Hot)

  • Everyone watches the same screening.
  • If someone is late, they miss the beginning.
  • The movie starts even if nobody is in the theatre.

29

30 of 58

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

31 of 58

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

32 of 58

Higher Order Observables

Higher Order Observables are Observables that take Observables as arguments. They are used when multiple streams need to interact.

32

33 of 58

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

34 of 58

combineLatest

Observable creation method - cannot exist inside a pipe.

Provides most recent values from all streams once all have emitted at least once.

34

35 of 58

Higher Order Observable Mapping Operators

These operators map the value of an outer observable to an inner observable. These can replace nested subscriptions.

35

36 of 58

concatMap

Use if need data from each stream sequentially before the next outer stream is mapped.

36

37 of 58

mergeMap

Use if the order of the input streams and the output is not important.

37

38 of 58

switchMap

Use if need most recent data from outer stream. Will cancel inner stream and replace.

Use case: Autocomplete

38

39 of 58

exhaustMap

Use if need to wait for inner stream to complete before mapping next outer stream.

Use case: Saving

39

40 of 58

Pitfalls

40

41 of 58

41

42 of 58

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

43 of 58

Nesting Subscriptions

Nesting Subscriptions are often a result of not taking a top-down approach in designing data flow.

  • Use higher order operators to consolidate results from multiple streams.
  • If one stream relies on another, map the results from the outer to the inner streams.
  • Consider if manual Subscriptions are necessary at all.

43

44 of 58

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

  1. When a view (component) is destroyed. Initializing the component again creates a new Subscription, which can lead to duplicates if not handled.

How To Unsubscribe

  1. Call each Subscription’s unsubscribe method.
  2. Handle Observable where manually subscribing is unnecessary - e.g. async pipe or prefetch the data through a resolver.

Angular handles some Subscriptions automatically.

44

45 of 58

Angular Implementation of RxJS

45

46 of 58

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

47 of 58

HttpClient

HttpClient creates Observables that complete once they have emitted one value.

Subscriptions to HttpClient Observables do not need to unsubscribe.

47

48 of 58

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

49 of 58

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

50 of 58

Subscription Error Handler

Outputs error from Observable.

  • Easy to set up
  • Cannot recover or give fallback value

this.fooService.getFooList()

.subscribe(

(response: Foo[]) => { this.foo = response; },

(error: FooError) => { this.errorMessage = error; },

);

50

51 of 58

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

52 of 58

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

53 of 58

Advanced Structures

53

54 of 58

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

55 of 58

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

56 of 58

Conclusion

56

57 of 58

Conclusion

  • Observables and operators revolve around three things: next, complete, and error.
  • Observables are lazy - they must be subscribed to before they execute.
  • Don’t nest Subscriptions - the higher order operators can help.
  • Manage Subscriptions.
  • Remember there are two kinds of Observables: hot and cold.

57

58 of 58

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