Skip to content

Commit

Permalink
batch update for flatmap
Browse files Browse the repository at this point in the history
  • Loading branch information
ngMachina committed Jun 30, 2021
1 parent 3dea723 commit 821c377
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 55 deletions.
2 changes: 1 addition & 1 deletion SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
- [Catch and Release](http/catching-rejections/catch_and_release.md)
- [Cancel a Request](http/catching-rejections/cancel_request.md)
- [Retry](http/catching-rejections/retry.md)
- [Search with flatMap](http/search_with_flatmap.md)
- [Search with mergeMap](http/search_with_mergemap.md)
- [Enhancing Search with switchMap](http/search_with_switchmap.md)
- [Requests as Promises](http/requests_as_promises.md)
- [Change Detection](change-detection/README.md)
Expand Down
2 changes: 1 addition & 1 deletion exercises/observables/1.0-SpotifySearch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Complete the implementation of _*app/artist-search.ts*_ and _*app/search-servic
}]
```

_Hint:_ Consider using `flatMap` to combine two observables.
_Hint:_ Consider using `mergeMap` to combine two observables.

## Expected Results

Expand Down
2 changes: 1 addition & 1 deletion handout/architect/10_ways_to_misuse_angular.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export class TodoStore {
```
Observables also allow us to merge multiple streams to implement complex workflows
such as a [debounced search which ignores stale requests](https://angular-2-training-book.rangle.io/handout/http/search_with_flatmap.html).
such as a [debounced search which ignores stale requests](https://angular-2-training-book.rangle.io/handout/http/search_with_mergemap.html).
Again,
by making these operations purely functional instead of maintaining separate state,
we can make our application much easier to test.
Expand Down
33 changes: 16 additions & 17 deletions http/search_with_flatmap.md → http/search_with_mergemap.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# Search with flatMap
# Search with mergeMap

![FlatMap created by ReactiveX licensed under CC-3 \(http://reactivex.io/documentation/operators/flatmap.html\)](../.gitbook/assets/flat-map.png)

A case for FlatMap:
A case for MergeMap:

* [A simple observable stream](http://jsbin.com/nutegi/36/edit?js,console)
* [A stream of arrays](http://jsbin.com/lerake/3/edit?js,console)
* [Filter the items from each event](http://jsbin.com/widadiz/2/edit?js,console)
* [Stream of filtered items](http://jsbin.com/reyoja/2/edit?js,console)
* [Filter + map simplified with flatMap](http://jsbin.com/sahiye/2/edit?js,console)
* [Filter + map simplified with mergeMap](http://jsbin.com/sahiye/2/edit?js,console)

Let's say we wanted to implement an AJAX search feature in which every keypress in a text field will automatically perform a search and update the page with the results. How would this look? Well we would have an `Observable` subscribed to events coming from an input field, and on every change of input we want to perform some HTTP request, which is also an `Observable` we subscribe to. What we end up with is an `Observable` of an `Observable`.

By using `flatMap` we can transform our event stream \(the keypress events on the text field\) into our response stream \(the search results from the HTTP request\).
By using `mergeMap` we can transform our event stream \(the keypress events on the text field\) into our response stream \(the search results from the HTTP request\).

_app/services/search.service.ts_

```javascript
import {Http} from '@angular/http';
import {Injectable} from '@angular/core';
import {map} from '@rxjs/operators';

@Injectable()
export class SearchService {
Expand All @@ -27,8 +27,9 @@ export class SearchService {

search(term: string) {
return this.http
.get('https://api.spotify.com/v1/search?q=' + term + '&type=artist')
.map((response) => response.json())
.get('https://api.spotify.com/v1/search?q=' + term + '&type=artist')
.pipe(map((response) => response.json()))

}
}
```
Expand All @@ -45,7 +46,7 @@ import { FormControl,
FormGroup,
FormBuilder } from '@angular/forms';
import { SearchService } from './services/search.service';
import 'rxjs/Rx';
import { map } from '@rxjs/operators';

@Component({
selector: 'app-root',
Expand All @@ -66,19 +67,17 @@ export class AppComponent {
this.searchField = new FormControl();
this.coolForm = fb.group({search: this.searchField});

this.searchField.valueChanges
.debounceTime(400)
.flatMap(term => this.searchService.search(term))
.subscribe((result) => {
this.result = result.artists.items
this.searchField.valueChanges.pipe(
debounceTime(400),
mergeMap(term => this.searchService.search(term))
).subscribe((result) => {
this.result = result.artists.items;
});
}
}
```

[View Example](http://plnkr.co/edit/L6CLXo?p=preview)
Here we have set up a basic form with a single field, `search`, which we subscribe to for event changes. We've also set up a simple binding for any results coming from the `SearchService`. The real magic here is `mergeMap` which allows us to flatten our two separate subscribed `Observables` into a single cohesive stream we can use to control events coming from user input and from server responses.

Here we have set up a basic form with a single field, `search`, which we subscribe to for event changes. We've also set up a simple binding for any results coming from the `SearchService`. The real magic here is `flatMap` which allows us to flatten our two separate subscribed `Observables` into a single cohesive stream we can use to control events coming from user input and from server responses.

Note that flatMap flattens a stream of `Observables` \(i.e `Observable` of `Observables`\) to a stream of emitted values \(a simple `Observable`\), by emitting on the "trunk" stream everything that will be emitted on "branch" streams.
Note that mergeMap flattens a stream of `Observables` \(i.e `Observable` of `Observables`\) to a stream of emitted values \(a simple `Observable`\), by emitting on the "trunk" stream everything that will be emitted on "branch" streams.

34 changes: 17 additions & 17 deletions http/search_with_switchmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

There is a problem with our previous implementation of incremental search.

What if the server, for some reason, takes a very long time to respond to a particular query? If we use `flatMap`, we run the risk of getting results back from the server in the wrong order. Let's illustrate this with an example.
What if the server, for some reason, takes a very long time to respond to a particular query? If we use `mergeMap`, we run the risk of getting results back from the server in the wrong order. Let's illustrate this with an example.

## A Quick Example

Expand All @@ -24,29 +24,29 @@ Here is a simple diagram to illustrate the issue:
------B1--B2------>
```

You can see that A2 arrives after B2 even though the A1 request began first. This will end up showing the wrong results to the user. "If the last input in the search was `ABCX` why am I seeing the results for `ABC`?" the user might think. To get around this problem we need to replace `flatMap` with `switchMap`.
You can see that A2 arrives after B2 even though the A1 request began first. This will end up showing the wrong results to the user. "If the last input in the search was `ABCX` why am I seeing the results for `ABC`?" the user might think. To get around this problem we need to replace `mergeMap` with `switchMap`.

## What is `switchMap`?

`switchMap` is very similar to `flatMap`, but with a very important distinction. Any events to be merged into the trunk stream are ignored if a new event comes in. Here is a marble diagram showing the behavior of `switchMap`:
`switchMap` is very similar to `mergeMap`, but with a very important distinction. Any events to be merged into the trunk stream are ignored if a new event comes in. Here is a marble diagram showing the behavior of `switchMap`:

![SwitchMap created by ReactiveX licensed under CC-3 \(http://reactivex.io/documentation/operators/flatmap.html\)](../.gitbook/assets/switch-map.png)
![SwitchMap created by ReactiveX licensed under CC-3 \(http://reactivex.io/documentation/operators/mergeMap.html\)](../.gitbook/assets/switch-map.png)

In short, every time an event comes down the stream, `flatMap` will subscribe to \(and invoke\) a new observable without unsubscribing from any other observable created by a previous event. `switchMap` on the other hand will automatically unsubscribe from any previous observable when a new event comes down the stream.
In short, every time an event comes down the stream, `mergeMap` will subscribe to \(and invoke\) a new observable without unsubscribing from any other observable created by a previous event. `switchMap` on the other hand will automatically unsubscribe from any previous observable when a new event comes down the stream.

In the diagram above, the round "marbles" represent events in the originating stream. In the resulting stream, "diamonds" mark the creation \(and subscription\) of an inner observable \(that is eventually merged onto the trunk stream\) and "squares" represent values emitted from that same inner observable.

Just like `flatMap`, the red marble gets replaced with a red diamond and a subsequent red square. The interaction between the green and blue marble events are more interesting. Note that the green marble gets mapped to a green diamond immediately. And if enough time had passed, a green square would be pushed into the trunk stream but we do not see that here.
Just like `mergeMap`, the red marble gets replaced with a red diamond and a subsequent red square. The interaction between the green and blue marble events are more interesting. Note that the green marble gets mapped to a green diamond immediately. And if enough time had passed, a green square would be pushed into the trunk stream but we do not see that here.

Before the green square event is able to happen, a blue marble comes through and gets mapped to a blue diamond. What happened is that the green square is now ignored and do not get merged back into the trunk stream. The behavior of `switchMap` can be likened to a `flatMap` that "switches" to the more immediate incoming event and ignores all previously created event streams.
Before the green square event is able to happen, a blue marble comes through and gets mapped to a blue diamond. What happened is that the green square is now ignored and do not get merged back into the trunk stream. The behavior of `switchMap` can be likened to a `mergeMap` that "switches" to the more immediate incoming event and ignores all previously created event streams.

In our case, because the blue marble event happened very quickly after the green marble, we "switched" over to focus on dealing with the blue marble instead. This behavior is what will prevent the problem we described above.

If we apply `switchMap` to the above example, the response for `ABC` would be ignored and the suggestions for `ABCX` would remain.

## Enhanced Search with `switchMap`

Here is the revised component using `switchMap` instead of `flatMap`.
Here is the revised component using `switchMap` instead of `mergeMap`.

_app/app.component.ts_

Expand All @@ -56,8 +56,8 @@ import { FormControl,
FormGroup,
FormBuilder } from '@angular/forms';
import { SearchService } from './services/search.service';
import 'rxjs/Rx';

import { debounceTime, switchMap } from '@rxjs/operators';
@Component({
selector: 'app-root',
template: `
Expand All @@ -77,19 +77,19 @@ export class AppComponent {
this.searchField = new FormControl();
this.coolForm = fb.group({search: this.searchField});

this.searchField.valueChanges
.debounceTime(400)
.switchMap(term => this.searchService.search(term))
.subscribe((result) => {
this.result = result.artists.items
});
this.searchField.valueChanges.pipe(
debounceTime(400),
switchMap(term => this.searchService.search(term))
).subscribe((result) => {
this.result = result.artists.items;
});
}
}
```

[View Example](http://plnkr.co/edit/FYLTcx?p=preview)

This implementation of incremental search with `switchMap` is more robust than the one we saw on the previous page with `flatMap`. The suggestions that the user sees will always eventually reflect the last thing the user typed. Thanks to this, we can guarantee a great user experience regardless of how the server responds.
This implementation of incremental search with `switchMap` is more robust than the one we saw on the previous page with `mergeMap`. The suggestions that the user sees will always eventually reflect the last thing the user typed. Thanks to this, we can guarantee a great user experience regardless of how the server responds.

## Further Resources

Expand Down
16 changes: 8 additions & 8 deletions observables/observables_array_operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ export class MyApp {
private doctors = [];

constructor(http: HttpClient) {
http.get('http://jsonplaceholder.typicode.com/users/')
.flatMap((response) => response.json())
.filter((person) => person.id > 5)
.map((person) => "Dr. " + person.name)
.subscribe((data) => {
this.doctors.push(data);
});
http.get('http://jsonplaceholder.typicode.com/users/').pipe(
mergeMap((response) => response.json()),
filter((person) => person.id > 5),
map((person) => "Dr. " + person.name)
).subscribe((data) => {
this.doctors.push(data);
});
}
}
```
Expand All @@ -29,7 +29,7 @@ Here are two really useful array operations - `map` and `filter`. What exactly d

Now when our `subscribe` callback gets invoked, the data it receives will be a list of JSON objects whose `id` properties are greater than or equal to six and whose `name` properties have been prepended with `Dr.`.

Note the chaining function style, and the optional static typing that comes with TypeScript, that we used in this example. Most importantly functions like `filter` return an `Observable`, as in `Observables` beget other `Observables`, similarly to promises. In order to use `map` and `filter` in a chaining sequence we have flattened the results of our `Observable` using `flatMap`. Since `filter` accepts an `Observable`, and not an array, we have to convert our array of JSON objects from `data.json()` to an `Observable` stream. This is done with `flatMap`.
Note the chaining function style, and the optional static typing that comes with TypeScript, that we used in this example. Most importantly functions like `filter` return an `Observable`, as in `Observables` beget other `Observables`, similarly to promises. In order to use `map` and `filter` in a chaining sequence we have flattened the results of our `Observable` using `mergeMap`. Since `filter` accepts an `Observable`, and not an array, we have to convert our array of JSON objects from `data.json()` to an `Observable` stream. This is done with `flatMap`.

There are many other array operations you can employ in your `Observables`; look for them in the [RxJS API](https://github.com/Reactive-Extensions/RxJS).

Expand Down
18 changes: 8 additions & 10 deletions observables/using_observables_from_other_sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A common operation in any web application is getting or posting data to a server
```javascript
import {Component} from '@angular/core';
import {Http} from '@angular/http';
import 'rxjs/Rx';
import {mergeMap} from '@rxjs/operators';

@Component({
selector: 'app',
Expand All @@ -29,17 +29,15 @@ export class MyApp {

constructor(http: Http) {
http.get('http://jsonplaceholder.typicode.com/users/')
.flatMap((data) => data.json())
.subscribe((data) => {
.pipe(
mergeMap((data) => data.json())
).subscribe((data) => {
this.doctors.push(data);

});
}
}
```

[View Example](http://plnkr.co/edit/AikZi1?p=preview)

This basic example outlines how the `Http` library's common routines like `get`, `post`, `put` and `delete` all return `Observables` that allow us to asynchronously process any resulting data.

## Observable Form Events
Expand All @@ -49,7 +47,7 @@ Let's take a look at how `Observables` are used in Angular forms. Each field in
```javascript
import {Component} from '@angular/core';
import {FormControl, FormGroup, FormBuilder} from '@angular/forms';
import 'rxjs/add/operator/map';
import { map } from '@rxjs/operators';

@Component({
selector: 'app',
Expand All @@ -76,9 +74,9 @@ export class MyApp {
email: this.email
});

this.email.valueChanges
.map(n=>n.split('').reverse().join(''))
.subscribe(value => this.data = value);
this.email.valueChanges.pipe(
map(n=>n.split('').reverse().join(''))
).subscribe(value => this.data = value);
}
}
```
Expand Down

0 comments on commit 821c377

Please sign in to comment.