1 of 45

RxJS Higher-order Mapping Operators

(Links to slides and code provided)�

Deborah Kurata

Developer | Pluralsight Author | Consultant | MVP | GDE

deborahkurata

2 of 45

What is a higher-order mapping operator?

When would you use one?

What the heck is an “inner Observable”?

What’s the difference between a map, switchMap, mergeMap and concatMap?

3 of 45

Deborah Kurata

Developer

Pluralsight Author

Angular Getting Started

Angular Reactive Forms

Angular Routing

RxJS in Angular: Reactive Development

Angular NgRx: Getting Started

C# OOP & Best Practices

Microsoft Most Valuable Professional (MVP)

Google Developer Expert (GDE)

@deborahkurata

4 of 45

RxJS First Order vs Higher-order �Mapping Operators�Part 1: First Order Mapping

Deborah Kurata

Developer | Pluralsight Author | Consultant | MVP | GDE

deborahkurata

5 of 45

RxJS First Order vs Higher-order �Mapping Operators�Part 2: Higher-order Mapping

Deborah Kurata

Developer | Pluralsight Author | Consultant | MVP | GDE

deborahkurata

6 of 45

First Order vs Higher Order Mapping

First order mapping�transforms each emitted value and emits the result

Higher order mapping�transforms each emitted value to an Observable

map(x => x * 2);

switchMap(id =>

this.http.get(`${url}/${id}`)

)

@deborahkurata

7 of 45

Mapping

(First Order)

@deborahkurata

8 of 45

@deborahkurata

9 of 45

{ data: [

{

id: 1,

productName: 'Palantir'

},

{

id: 2,

productName: 'Rope'

},

{

id: 5,

productName: 'Sword'

}

]

}

[

{

id: 1,

productName: 'Palantir'

},

{

id: 2,

productName: 'Rope'

},

{

id: 5,

productName: 'Sword'

}

]

Map ->

products$ = this.http.get<ProductData>(this.productsUrl)

.pipe(

map(response => response.data),

catchError(this.handleError)

);

@deborahkurata

10 of 45

Map ->

product$ = this.http.get<Product>(this.url)

.pipe(

map(p => ({ ...p,

profit: p.price - p.cost}) as Product),

catchError(this.handleError)

);

{

id: 5,

productName: 'Sword',

productCode: 'WEA-0048',

releaseDate: 'May 21, 2018',

description: 'Flame of the West',

price: 849.9,

cost: 520.0,

starRating: 4.9

}

{

id: 5,

productName: 'Sword',

productCode: 'WEA-0048',

releaseDate: 'May 21, 2018',

description: 'Flame of the West',

price: 849.9,

cost: 520.0,

profit: 329.9,

starRating: 4.9

}

@deborahkurata

11 of 45

Map ->

pageTitle$ = this.product$

.pipe(

map((p: Product) =>

p ? `Product Detail for: ${p.productName}` : null)

);

"Product Detail for: Sword"

{

id: 5,

productName: 'Sword',

productCode: 'WEA-0048',

releaseDate: 'May 21, 2018',

description: 'Flame of the West',

price: 849.9,

cost: 520.0,

profit: 329.9,

starRating: 4.9

}

@deborahkurata

12 of 45

@deborahkurata

13 of 45

Map ->

product$ = this.productSelectedAction$

.pipe(

map(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

selectedId = 5

{

id: 5,

productName: 'Sword',

productCode: 'WEA-0048',

releaseDate: 'May 21, 2018',

description: 'Flame of the West',

price: 849.9,

cost: 520.0,

profit: 329.9,

starRating: 4.9

}

Doesn't work!!

@deborahkurata

14 of 45

product$ = this.productSelectedAction$

.pipe(

map(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

Source or �Outer Observable

Inner Observable

@deborahkurata

15 of 45

product$ = this.productSelectedAction$

.pipe(

map(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

Observable<Observable<Product>>

No subscription

@deborahkurata

16 of 45

Higher-order Mapping Operators

@deborahkurata

17 of 45

Higher-order Mapping Operators

  • Family of operators: xxxMap()
  • Map each value
    • From a source (outer) Observable
    • To a new (inner) Observable
  • Automatically subscribe to and unsubscribe from the inner Observables
  • Emit the resulting values to the output Observable

18 of 45

Higher-order Mapping Operator

product$ = this.productSelectedAction$

.pipe(

switchMap(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

Observable<Product>

Automatically subscribes and unsubscribes from inner Observable

@deborahkurata

19 of 45

Higher-order Mapping Operators

mergeMap (flatMap)

concatMap

switchMap

20 of 45

mergeMap

aka flatMap

@deborahkurata

21 of 45

mergeMap

product$ = this.productSelectedAction$

.pipe(

mergeMap(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

On subscribe to the source stream:

Takes in an

Observable stream

Subscribes to that stream

Creates an

output stream

@deborahkurata

22 of 45

mergeMap

product$ = this.productSelectedAction$

.pipe(

mergeMap(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

Item is mapped to the inner Observable

Subscribes to that inner Observable

Inner Observable emissions concatenated to the output stream

When an item is emitted from the source stream:

Unsubscribes from that inner Observable

@deborahkurata

23 of 45

Use mergeMap

  • To process items in parallel
  • When order doesn't matter
  • Examples:
    • With a set of supplier ids, get each supplier for a product (order doesn't matter)

24 of 45

concatMap

@deborahkurata

25 of 45

concatMap

product$ = this.productSelectedAction$

.pipe(

concatMap(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

On subscribe to the source stream:

Takes in an

Observable stream

Subscribes to that stream

Creates an

output stream

@deborahkurata

26 of 45

concatMap

product$ = this.productSelectedAction$

.pipe(

concatMap(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

When an item is emitted from the source stream:

Emitted item is queued

@deborahkurata

27 of 45

concatMap

product$ = this.productSelectedAction$

.pipe(

concatMap(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

When queued source item is processed:

Item is mapped to the inner Observable

Subscribes to that inner Observable

**WAITS! for complete

Inner Observable emissions concatenated to the output stream

Unsubscribes from that inner Observable

@deborahkurata

28 of 45

Use concatMap

  • To wait for the prior Observable to complete before starting the next one
  • To process items in sequence
  • Examples:
    • From a set of ids, get data in sequence
    • From a set of ids, update data in sequence

29 of 45

switchMap

@deborahkurata

30 of 45

switchMap

product$ = this.productSelectedAction$

.pipe(

switchMap(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

On subscribe to the source stream:

Takes in an

Observable stream

Subscribes to that stream

Creates an

output stream

@deborahkurata

31 of 45

switchMap

product$ = this.productSelectedAction$

.pipe(

switchMap(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

Item is mapped to the inner Observable

**Unsubscribes from prior inner Observable

Subscribes to new inner Observable

Inner Observable emissions concatenated to the output stream

Unsubscribes from that inner Observable

When an item is emitted from the source stream:

@deborahkurata

32 of 45

Use switchMap

  • To stop any prior Observable before switching to the next one
  • To process the most recent item
  • Examples:
    • Type ahead or auto completion
    • User selection from a list

33 of 45

Higher-order Mapping Operators

mergeMap (flatMap)

    • Executes inner Observables in parallel

concatMap

    • Waits for each inner Observable to complete

switchMap

    • Unsubscribes from prior inner Observable and switches to the new one

34 of 45

Quiz?

@deborahkurata

35 of 45

QUIZ: Quick Selection

2

1

3

product$ = this.productSelectedAction$

.pipe(

XXXMap(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

@deborahkurata

36 of 45

Quick Selection: mergeMap

2

1

3

Get 2

Get 1

Get 3

@deborahkurata

37 of 45

Quick Selection: concatMap

2

1

3

Get 2

Get 1

Get 3

@deborahkurata

38 of 45

Quick Selection: switchMap

2

1

3

Get 3

@deborahkurata

39 of 45

QUIZ: Quick Selection

product$ = this.productSelectedAction$

.pipe(

switchMap(selectedId =>

this.http.get<Product>(`${url}/${selectedId}`)

.pipe(

tap(response => console.log(response))

)

));

@deborahkurata

40 of 45

QUIZ: Related Data

suppliers$ = from(product.supplierIds)

.pipe(

XXXMap(supplierId =>

this.http.get<Supplier>(`${url}/${supplierId}`)

.pipe(

tap(response => console.log(response))

),

),

toArray()

);

@deborahkurata

41 of 45

Related Data: mergeMap

Get

Get

@deborahkurata

42 of 45

Related Data: concatMap

Get

Get

@deborahkurata

43 of 45

Related Data: switchMap

Get

@deborahkurata

44 of 45

QUIZ: Related Data

suppliers$ = from(product.supplierIds)

.pipe(

mergeMap(supplierId =>

this.http.get<Supplier>(`${url}/${supplierId}`)

.pipe(

tap(response => console.log(response))

),

),

toArray()

);

@deborahkurata

45 of 45

Thank You!

Slides:

http://bit.ly/deborahk-ngconf2020

Code:

https://github.com/DeborahK/Angular-HigherOrderMapping

https://stackblitz.com/edit/angular-todos-deborahk-higher-order

@deborahkurata