I've seen many other posts on this topic, and have read the official (see below) and semi-official documentation such as https://www.learnrxjs.io/operators/transformation/switchmap.html, but I still haven't been able to internalize the difference between "map" and "switchMap" and am hoping to clarify with the concrete examples below.
Note as per official RxJS documentation:
- map: "Applies a given project function to each value emitted by the source Observable, and emits the resulting values as an Observable." http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-map
- switchMap: "Projects each source value to an Observable which is merged in the output Observable, emitting values only from the most recently projected Observable." http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-switchMap
With my incomplete understanding in mind, I made a few very simple examples, see StackBlitz https://stackblitz.com/edit/rxjs-xpicph?devtoolsheight=60, but still don't fully understand why some of the examples are producing the output they do, in the way they do.
Firstly, some very simple string
examples:
// String Example 1
const source = of('World').pipe(
map(x => `Hello ${x}!`)
);
source.subscribe(x => console.log(`SOURCE (map): ${x}`));
// SOURCE (map): Hello World!
OK, fair enough, I think I mainly get this.
- "Of" emits 'World' as value (or is the emission still an Observable at this stage?) to "pipe"
- "pipe" provides 'World' as an value (or is it still an Observable?) to "map"
- "map" projects this (value? Observable?) to 'Hello World', waiting for all the characters to complete, and returns this (value? Observable?) to "pipe"
- "pipe" then returns an Observable.
Hence we get the output: "Hello World"
// String Example 2
const otherSource = of('World').pipe(
switchMap(x => `Hello ${x}!`)
);
otherSource.subscribe(x => console.log(`SOURCE (switchMap): ${x}`));
// SOURCE (switchMap): H
// SOURCE (switchMap): e
// SOURCE (switchMap): l
// SOURCE (switchMap): l
// SOURCE (switchMap): o
// SOURCE (switchMap):
// SOURCE (switchMap): W
// SOURCE (switchMap): o
// SOURCE (switchMap): r
// SOURCE (switchMap): l
// SOURCE (switchMap): d
// SOURCE (switchMap): !
WHOA! EXCUSE ME? WHAT JUST HAPPENED?
- "Of" emits 'World' as value (or is the emission still an Observable at this stage?) to "pipe"
- "pipe" provides 'World' as an value (or is it still an Observable?) to "switchMap",
- "switchMap" projects this (value? Observable?) to 'Hello World', but unlike "map" doesn't wait for all the characters to complete before outputting to "pipe" a series of Observables (or values?), and is it one Observable per character? or is it one Observable that emits once per each character?
- "pipe" then returns an Observable on each character?
QUESTION: What exactly is going on under the hood here, step-by-step in the chains above?
Let's move on to another simple set of examples, and then hopefully try to tie everything together:
// OBJECT EXAMPLES
const foo = {
"first": 1,
"second": 2
}
// OBJECT EXAMPLE 1
Object.keys(foo).forEach(obj=>of(foo[obj]).pipe(
map(x=>x*2)
).subscribe(x => console.log(`SOURCE (map): ${x}`)))
// SOURCE (map): 2
// SOURCE (map): 4
OK, fair enough. That's seems pretty straightforward
// OBJECT EXAMPLE 2
Object.keys(foo).forEach(obj=>of(foo[obj]).pipe(
switchMap(x=>of(x*2)) // WHY DO WE NEED ANOTHER "of()" HERE? "switchMap(x=>x*2)" DOESN'T COMPILE
).subscribe(x=> console.log(`SOURCE (switchMap): ${x}`)))
// SOURCE (switchMap): 2
// SOURCE (switchMap): 4
Reasonably clear, but WHY do we need to supply "of(x*2) to "switchMap"? In STRING EXAMPLE 2, "switchMap" seemed to emit like crazy and automatically wrap its output as Observable (or did "pipe" wrap the output as Observable?), but whatever the case, "switchMap" and "pipe" didn't need any extra "of()" or any other help wrapping the output as an Observable, but in OBJECT EXAMPLE 2, we explicitly need to provide the second "of()" to make sure the the output from "switchMap" is an observable, otherwise the code won't compile. (But for "map", we don't need to provide the second "of()"). Again, step-by-step, why the difference?
So, to summarize, I'd be extremely grateful if anyone can explain:
- At which point(s) in the chain(s) in the examples above are we dealing with values (i.e., emissions from Observables) and at which points with Observables?
- Why does "switchMap" provide the apparent parsing behavior seen in STRING EXAMPLE 2?
- Why do we need to provide "of()" to "switchMap" in OBJECT EXAMPLE 2, but not in STRING EXAMPLE 2? (And, similarly, why doesn't "map" need a second "of()"?)
Thank you in advance!!