call two different methods based on emissions of two observables - rxjs

currently, I have a cabinet with some drawers, each drawer has some items that users can manipulate. Whenever the user switches to a different drawer an observable (D) fires. As soon as the user swithces to a drawer, another observable (I) fires with an array of Items available in that drawer.
My desired behaviour is for the follwoing stream:
Drawer(D): --D----------------------------D--...
Items(I): -------I----I-----I-----I-----I----I--..
for an emission (D) and first following emission (I) => call a function (setCurrentDrawerItems())
for all following emissions of (I) that are not interrupted by an emission (D) =>
call the function (userPickedItemFromDrawer())
how can I achieve this using rxjs operators?

One approach is to tag your observable emissions to cleanly separate the two in the merged stream. For example, shoving them into an object with the label t. Then you can just query that label to know what to do next.
Something like this might work:
merge(
drawer$.pipe(map(v => ({ t: 'D', v }))),
items$.pipe(map(v => ({ t: 'I', v })))
).pipe(
startWith({ t: 'I', v: null }),
pairwise()
).subscribe(([prev, curr]) => {
if (curr.t == 'D' || (curr.t == 'I' && prev.t == 'D')) {
setCurrentDrawerItems(curr.v);
} else {
userPickedItemFromDrawer(curr.v);
}
});

Related

Using RxJS to manipulate a stream of items to obtain an array of streamed items

i'm kinda new to rxjs and can't get my head around this problem:
I have two streams:
one with incoming objects
---a----b----c----d----->
one with the selected object from a list
-------------------c---->
From the incoming objects stream make a stream of the list of objects (with scan operator)
incoming: ----a--------b-------c----------d----------------\>
list: -------[a]----[a,b]----[a,b,c]----[a,b,c,d]---------\>
When a list object is selected (n), start a new stream
the first value of the new stream is the last value of the list sliced ( list.slice(n))
incoming: ----a--------b-------c----------d--------------------e-------->
list: -------[a]----[a,b]----[a,b,c]----[a,b,c,d]--------->
selected object: ---------------------------------c------->
new stream of list: ------[c,d]-----[c,d,e]--->
i can't get the last value of the list stream when the object is selected,,,
made a marble diagram for better understanding,
selectedObject$ = new BehaviorSubject(0);
incomingObjects$ = new Subject();
list$ = incomingObjects$.pipe(
scan((acc, val) => {
acc.push(val);
return acc;
}, [])
)
newList$ = selectedObject$.pipe(
withLastFrom(list$),
switchMap(([index,list])=> incomingObjects$.pipe(
scan((acc, val) => {
acc.push(val);
return acc;
}, list.slice(index))
))
)
A common pattern I use along with the scan operator is passing reducer functions instead of values to scan so that the current value can be used in the update operation. In this case you can link the two observables with a merge operator and map their values to functions that are appropriate - either adding to a list, or slicing the list after a selection.
// these are just timers for demonstration, any observable should be fine.
const incoming$ = timer(1000, 1000).pipe(map(x => String.fromCharCode(x + 65)), take(10));
const selected$ = timer(3000, 3000).pipe(map(x => String.fromCharCode(x * 2 + 66)), take(2));
merge(
incoming$.pipe(map(x => (s) => [...s, x])), // append to list
selected$.pipe(map(x => (s) => { // slice list starting from selection
const index = s.indexOf(x);
return (index !== -1) ? s.slice(index) : s;
}))
).pipe(
scan((list, reducer) => reducer(list), []) // run reducer
).subscribe(x => console.log(x)); // display list state as demonstration.
If I understand the problem right, you could follow the following approach.
The key point is to recognize that the list Observable (i.e. the Observable obtained with the use of scan) should be an hot Observable, i.e. an Observable that notifies independent on whether or not it is subscribed. The reason is that each new stream you want to create should have always the same source Observable as its upstream.
Then, as you already hint, the act of selecting a value should be modeled with a BehaviorSubject.
As soon as the select BehaviorSubject notifies a value selected, the previous stream has to complete and a new one has to be subscribed. This is the job of switchMap.
The rest is to slice the arrays of numbers in the right way.
This is the complete code of this approach
const selectedObject$ = new BehaviorSubject(1);
const incomingObjects$ = interval(1000).pipe(take(10));
const incomingObjectsHot$ = new ReplaySubject<number[]>(1);
incomingObjects$
.pipe(
scan((acc, val) => {
acc.push(val);
return acc;
}, [])
)
.subscribe(incomingObjectsHot$);
selectedObject$
.pipe(
switchMap((selected) =>
incomingObjectsHot$.pipe(
map((nums) => {
const selIndex = nums.indexOf(selected);
if (selIndex > 0) {
return nums.slice(selIndex);
}
})
)
),
filter(v => !!v)
)
.subscribe(console.log);
An example can be seen in this stackblitz.

How do I create an observable which emits a value from an ngrx selector, then another value after a delay?

Given an ngrx selector:
store.select('count')
I want to create an observable that will emit values emitted by the selector, then emit another specific value after a delay.
Using concat doesn't work as (I assume) the selector doesn't complete, so the 0 is never emitted:
this.count$ = concat(store.select('count'), of(0).pipe(delay(2000)));
Stackblitz: https://stackblitz.com/edit/so-selector-delay?file=src/app/my-counter/my-counter.component.ts
- click 'Increment' button - Current Count should change to 1 then back to 0 after 2 seconds.
If you want to emit the store.select('count') value, then essentially reset it to 0 after not receiving an emission for 2 seconds, you can use a switchMap to create a source that emits two values:
The emitted count
The "default" value of 0 after 2000ms
The trick here is that the second value (0) will NOT be emitted if another store.select('count') value is received, because switchMap will create a new source and "switch" to that:
this.count$ = store.select('count').pipe(
switchMap(count => concat(
of(count),
of(0).pipe(delay(2000))
))
);
Here's a working StackBlitz demo.
It might even be worth creating a custom operator:
this.count$ = this.store.select('count').pipe(
resetAfterDelay(0, 2000)
);
export function resetAfterDelay<T>(defaultValue: T, delayMs: number) {
return switchMap((value: T) => concat(
of(value),
of(defaultValue).pipe(delay(delayMs))
));
}
StackBlitz
Below is an approach using combineLatest and BehaviorSubject
We are hold a value in a subject and create a timer that emits value 0 after 2s. So we have two Observables, one emits immediately and the other after 2s. We combine this two and the effect is a single observable as desired
valueHolderSubject$ = new BehaviorSubject(0);
...
this.count$ = combineLatest([
store.select("count").pipe(
tap(x => this.valueHolderSubject$.next(x)),
tap(() =>
timer(2000).subscribe({
next: () => {
this.valueHolderSubject$.next(0);
}
})
),
map(() => this.valueHolderSubject$.value),
distinctUntilChanged()
),
this.valueHolderSubject$
]).pipe(map(([, x]) => x));
Demo Here
A per my comments on the answer from BizzyBob, I was getting unreliable results. But I refined it to work using:
this.count$ = merge(
store.select('count'),
store.select('count').pipe(delay(2000), map(_ => 0))
);
See stackblitz: https://stackblitz.com/edit/so-selector-delay-merge-working?file=src/app/my-counter/my-counter.component.ts

What is the difference between tap and map in RxJS?

I read the difference from the article but the main points look like this.
so with tap I can change the variables such as that if I put x=3+4 then it changes the values of variable then I can say there is one side effect.
But with map I can change the value looping each value, isn't it?
Can you pinpoint what outstanding differences they have?
tap
RxJS tap performs side effects for every value emitted by source Observable and returns an Observable identical to the source Observable until there is no error.
map
map is a RxJS pipeable operator. map applies a given function to each element emitted by the source Observable and emits the resulting values as an Observable
A mapping function takes a thing and returns another thing. e.g. I can build a function that takes 10 and returns 11, that takes 11 and returns 12, etc.
const inc = n => n + 1;
Array#map applies such mapping function to all elements of an array but "map" doesn't mean "iteration".
In RxJS, when a data is sent to the stream it goes through a series of operators:
The map operator will simply apply a function to that data and return the result.
The tap operator however takes a data, apply a function to that data but returns the original data, if the function bothered to return a result, tap just ignores it.
Here's an example:
We push 10 to stream a$, tap just log the value. We know that console.log always return undefined but that's fine because tap simply returns its parameter.
We push 10 to stream b$, it goes through map(inc) which applies inc to 10 returning 11.
const a$ = of(10).pipe(tap(n => console.log(`tap: ${n}`)));
const b$ = of(10).pipe(map(inc));
a$.subscribe(n => console.log(`n from a$: ${n}`));
b$.subscribe(n => console.log(`n from b$: ${n}`));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.min.js"></script>
<script>
const {of} = rxjs;
const {map, tap} = rxjs.operators;
const inc = n => n + 1;
</script>
Tap should be Used for Notification, logging non-contextual/critical side effects.
It's like a "peek" into the "pipe". The data stays the same, You can do something with it. Some data goes in, you look, same data comes out.
Map is for transformation/mapping of the Data in the "pipe". Some data comes in, different/transformed data comes out.
The purpose of tap is to execute an action keeping the same value
of the observable
The purpose of map is to transform the emitted values of the
observable
const messagesCount$ = newMessages$
.pipe(tap(messages => notificationService.notify('You have ' + message.length + ' message(s)')))
.pipe(map(messages => messages.length))
The tap and map are both RxJS operators, RxJS operators are just function that performs some manipulation over the data.
Both of them are pipeable operators which takes input as Observable, perform some action and return an output observable.
Difference between map and tap:
The map is a pipeable operator that takes an input observable, performs some manipulation on it and returns a new manipulated observable. For example
const source$ = of(1,2,3) // observable which will emit 1,2,3
// It take an input observable and return a new observable which will emit square of input values.
// So, the output observable will emit 1,4,9
const mapSource$ = of(1,2,3)
.pipe(map(value => value * value))
The tap operator on another hand takes an input observable perform some action and returns the same input observable.
const source$ = of(1,2,3) // observable which will emit 1,2,3
// It take an input observable and return a same observable after console value.
// So, the output observable will emit 1,2,3
const tapSource$ = of(1,2,3)
.pipe(tap(value => console.log(value)))
you can think of tap operator as a void function that whatever it does to the input value it does not change the original value
const source = of(1, 2, 3, 4, 5);
// here we are manipulating the input value but the output value of the observable still the same
const example = source.pipe(
tap(val => val + 100),
);
// output: 1, 2, 3, 4, 5
const subscribe = example.subscribe(val => console.log(val));
in the other hand if we made any manipulation of the input values of the observable using the map operator it will change the output values
const example = source.pipe(
map(val => val + 100)
);
// output: 101, 102, 103, 104, 105
const subscribe = example.subscribe(val => console.log(val));
I addition to what the others are saying, in Rxjs 7.4 tap now has three more subscribe handlers, so you can use it to get notified on subscribe, unsubscribe and finalize:
https://github.com/ReactiveX/rxjs/commit/eb26cbc4488c9953cdde565b598b1dbdeeeee9ea#diff-93cd3ac7329d72ed4ded62c6cbae17b6bdceb643fa7c1faa6f389729773364cc
This is great for debugging purposes, so you can use tap to find out much more about what is happening with your stream.
Example:
const subscription = subject
.pipe(
tap({
subscribe: () => console.log('subscribe'),
next: (value) => console.log(`next ${value}`),
error: (err) => console.log(`error: ${err.message}`),
complete: () => console.log('complete'),
unsubscribe: () => console.log('unsubscribe'),
finalize: () => console.log('finalize'),
})
)
.subscribe();
TAP, can NOT transform:
interval(1000).pipe(tap(el=> el*2)).subscribe(console.log); // 0,1,2,3
MAP, CAN transform:
interval(1000).pipe(map(el=> el*2)).subscribe(console.log); // 0,2,4,6
If you do NOT need to transform the value, just console.log it or run external function to pass the Original value = TAP is good.
If you NEED TO TRANSFORM/CHANGE the value = MAP is the way to go.

How to combine a parent and a dependent child observable

There is a continuous stream of event objects which doesn't complete. Each event has bands. By subscribing to events you get an event with several properties, among these a property "bands" which stores an array of bandIds. With these ids you can get each band. (The stream of bands is continuous as well.)
Problem: In the end you'd not only like to have bands, but a complete event object with bandIds and the complete band objects.
// This is what I could come up with myself, but it seems pretty ugly.
getEvents().pipe(
switchMap(event => {
const band$Array = event.bands.map(bandId => getBand(bandId));
return combineLatest(of(event), ...band$Array);
})),
map(combined => {
const newEvent = combined[0];
combined.forEach((x, i) => {
if (i === 0) return;
newEvent.bands = {...newEvent.bands, ...x};
})
})
)
Question: Please help me find a cleaner way to do this (and I'm not even sure if my attempt produces the intended result).
ACCEPTED ANSWER
getEvents().pipe(
switchMap(event => {
const band$Array = event.bands.map(bandId => getBand(bandId));
return combineLatest(band$Array).pipe(
map(bandArray => ({bandArray, event}))
);
})
)
ORIGINAL ANSWER
You may want to try something along these lines
getEvents().pipe(
switchMap(event => {
const band$Array = event.bands.map(bandId => getBand(bandId));
return forkJoin(band$Array).pipe(
map(bandArray => ({bandArray, event}))
);
})
)
The Observable returned by this transformation emits an object with 2 properties: bandArray holding the array of bands retrieved with the getBand service and event which is the object emitted by the Observable returned by getEvents.
Consider also that you are using switchMap, which means that as soon as the Observable returned by getEvents emits you are going to switch to the last emission and complete anything which may be on fly at the moment. In other words you can loose some events if the time required to exectue the forkJoin is longer than the time from one emission and the other of getEvents.
If you do not want to loose anything, than you better use mergeMap rather than switchMap.
UPDATED ANSWER - The Band Observable does not complete
In this case I understand that getBand(bandId) returns an Observable which emits first when the back end is queried the first time and then when the band data in the back end changes.
If this is true, then you can consider something like this
getEvents().pipe(
switchMap(event => {
return from(event.bands).pipe(
switchMap(bandId => getBand(bandId)).pipe(
map(bandData => ({event, bandData}))
)
);
})
)
This transformation produces an Observable which emits either any time a new event occurs or any time the data of a band changes.

RxJS observable which emits both previous and current value starting from first emission

I have a BehaviorSubject which emits JavaScript objects periodically. I want to construct another observable which will emit both previous and current values of the underlying observable in order to compare two objects and determine the delta.
The pairwise() or bufferCount(2, 1) operators are looking like a good fit, but they start emitting only after buffer is filled, but I require this observable to start emitting from the first event of the underlying observable.
subject.someBufferingOperator()
.subscribe([previousValue, currentValue] => {
/** Do something */
})
;
On first emission the previousValue could be just null.
Is there some built-in operators that I can use to achieve the desired result?
Actually, it was as easy as pairing pairwise() with startWith() operators:
subject
.startWith(null) // emitting first empty value to fill-in the buffer
.pairwise()
.subscribe([previousValue, currentValue] => {
if (null === previousValue) {
console.log('Probably first emission...');
}
})
;
Here's a simple operator:
function withPreviousItem<T>(): OperatorFunction<
T,
{
previous?: T;
current: T;
}
> {
return pipe(
startWith(undefined),
pairwise(),
map(([previous, current]) => ({
previous,
current: current!
}))
);
}
The nice thing about this is that the result has meaningful property names and correct types:
previous is T | undefined
current is T (not T | null)
Stackblitz example
Here's the snippet for rxjs 6+
subject
.pipe(
startWith(undefined),
pairwise()
)
.subscribe(([previousValue, currentValue]) => {
/** Do something */
});
The value in startWith() should be undefined because there is no value. Typically null is defined as "we have a value and this value is empty".
scan (RX equivalent of a reduce) is an option here:
subject
.scan((accumulator, currentValue) => {
const previousValue = ...accumulator.slice(-1);
return [previousValue, currentValue];
}, [null]) // emitting first empty value to fill-in the buffer
.subscribe(([previousValue, currentValue]) => {
// ...
});
This can be extended to a more general case when you want to look at more than two items.

Resources