TIL, 2021-07-22, RxJS articles
tap
, map
& switchMap
explained
- Can create an observable of an array with
from()
. - If we subscribe to it, we could do something with the values which get emitted.
- Tap - can pass up to three methods which have the
void
return type, the original observable stays untouched.- You can manipulate items in the stream. It is used for side effects but returns an observable identical to the one from the source.
const objects = [
{ id: 1, name: 'Fabian' },
{ id: 2, name: 'Jan-Niklas' },
];
const source$ = from(objects)
.pipe(tap((item) => (item.name = item.name + '_2')))
.subscribe((x) => console.log(x));
- Map: The
map
is the same as tap, it can manipulate the value and pass it further to the stream again. To manipulate the items in the stream, themap
operator is your friend. switchMap
: The last result of the observable in themap
operator. Getting out the value and resolve the first observable.
Deep Dive Into The RxJs switchMap Operator: How Does it Work?
- How do Angular HTTP Observables work?
- HTTP observables are cold/not live, meaning they will not start emitting values until we subscribe to them.
- They only emit a single value or an error, and then after they complete, so they are not long-lived Observables.
- In most cases, no need to unsubscribe, because they will complete after emission.
- Shorthand for the subscribing to the observable.
http1$.subscribe(
console.log,
console.error,
() => console.log('http1$ completed')
);
http1$.subscribe(
val => console.log(val),
err => console.error(err),
() => console.log('http1$ completed')
);
switchMap
to combine two HTTP requests:
const saveUser$ = simulateHttp(" user saved ", 1000);
const httpResult$ = saveUser$.pipe(
switchMap(sourceValue => {
console.log(sourceValue);
return simulateHttp(" data reloaded ", 2000);
}
)
);
httpResult$.subscribe(
console.log,
console.error,
() => console.log('completed httpResult$')
);
- Source observable =
saveUser$
. Result observable = the result of the switchMap (resultObservable$
). - If we subscribe to the result obs, that will trigger a subscription to the source Observable.
- One the source Observable emits, the source value emitted is then passed on to the function that we have passed to the
switchMap
operator. - That function needs to return an
Observable
, that might be built using the source value or not. - That returned observable is said to be the inner observable. Its output is then emitted also by the result observable.
- When the source observable completes, the result observable also completes.
- Switch - Switching from the source observable to the inner observable.
- switchMap operator will create a derived observable from a source observable and emit those values.
- When the source emits a new value, it will create a new inner observable and
switch
to those values instead. - What gets unsubscribed from are the inner observables that get created on the fly, and not the source observable.
- When the source emits a new value, it will create a new inner observable and
- Combining the output of two variables:
- Apply the map operator to the inner observable, and return a tuple containing both values.
const course$ = simulateHttp({id:1, description: 'Angular For Beginners'}, 1000);
const httpResult$ = course$.pipe(
switchMap(courses => simulateHttp([], 2000)
.pipe(
map(lessons => [courses,lessons])
),
)
);
httpResult$.subscribe(
console.log,
console.error,
() => console.log('completed httpResult$')
);
Comprehensive Guide to Higher-Order RxJs Mapping Operators: switchMap, mergeMap, concatMap (and exhaustMap)
- Map operator: Mapping the values of the input observable.
- Higher-order observable mapping: Instead of mapping a plain value to another value like
map
, we are going to map a value into anObservable
. - The result is an observable, but its values are
Observables
as well - Use case of a higher-order observable: reactive form where we want to save to the back-end when the user is typing in the form.
- Simple solution would cause nested subscribes.
this.form.valueChanges
.subscribe(
formValue => {
const httpPost$ =
this.http.put(`/api/course/${courseId}`, formValue);
httpPost$.subscribe( => Inner observable
res => ... handle successful save ...
err => ... handle save error ...
);
}
);
- What we would like to do is to take the form value, map it into a save Observable, and this would create a higher-order Observable, where each value corresponds to a save request.
- Possible use cases, if multiple form values are emitted:
- Should we wait for one save request to complete before doing another save?
- Should we do multiple saves in parallel?
- Should we cancel an ongoing save and start a new one?
- Should we ignore new save attempts while one is already ongoing?
- In nested subscribes, we are actually triggering the save operations in parallel (not what we want since the backend will handle the saves sequentially).
- Observable concatenation
concat
will subscribe to the first Observable, but not to the second Observable.- First one will emit whatever it does, then when it completes,
concat
will then subscribe to the second. - Then, when the second completes, the result Observable will also then complete.
- To implement sequential saves:
- Take the form value and turn it into an
httpPost$
observable. - Then, concatenate the multiple
httpPost$
observables together to ensure that an HTTP save is not made before the previous ongoing save completes first.
- Take the form value and turn it into an
this.form.valueChanges
.pipe(
concatMap(formValue => this.http.put(`/api/course/${courseId}`,
formValue))
)
.subscribe(
saveResult => ... handle successful save ...,
err => ... handle save error ...
);
- All the form values are going to be sent to the backend sequentially.
concatMap
is taking each form value and transforming it into a save HTTP Observable.- If a second form value comes faster than the save, the new form value will not be immediately mapped to an HTTP request.
- Instead,
concatMap
will wait for the previous HTTP observable to complete. - Can combine with other operators to save only valid form values, and throttle the saves to make sure they don’t occur too frequently.
- Merging: When we want to run things in parallel. Note we don’t need to wait for the Observable to complete.
- Like concatMap, the inner Observable is also subscribed to by mergeMap, but unlike concatMap, we don’t wait for the previous thing to complete.
- This would mean the results are running in parallel.