RxJS iif arguments are called when shouldn't - rxjs

I want to conditionally dispatch some actions using iif utility from RxJS. The problem is that second argument to iif is called even if test function returns false. This throws an error and app crashes immediately. I am new to to the power of RxJS so i probably don't know something. And i am using connected-react-router package if that matters.
export const roomRouteEpic: Epic = (action$, state$) =>
action$.ofType(LOCATION_CHANGE).pipe(
pluck('payload'),
mergeMap(payload =>
iif(
() => {
console.log('NOT LOGGED');
return /^\/room\/\d+$/.test(payload.location.pathname); // set as '/login'
},
merge(
tap(v => console.log('NOT LOGGED TOO')),
of(
// following state value is immediately evaluated
state$.value.rooms.list[payload.location.pathname.split('/')[1]]
? actions.rooms.initRoomEnter()
: actions.rooms.initRoomCreate(),
),
of(actions.global.setIsLoading(true)),
),
empty(),
),
),
);

A little late to the party, but I found that the role of iif is not to execute one path over the other, but to subscribe to one Observable or the other. That said, it will execute any and all code paths required to get each Observable.
From this example...
import { iif, of, pipe } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
const source$ = of('Hello');
const obsOne$ = (x) => {console.log(`${x} World`); return of('One')};
const obsTwo$ = (x) => {console.log(`${x}, Goodbye`); return of('Two')};
source$.pipe(
mergeMap(v =>
iif(
() => v === 'Hello',
obsOne$(v),
obsTwo$(v)
))
).subscribe(console.log);
you'll get the following output
Hello World
Hello, Goodbye
One
This is because, in order to get obsOne$ it needed to print Hello World. The same is true for obsTwo$ (except that path prints Hello, Goodbye).
However you'll notice that it only prints One and not Two. This is because iif evaluated to true, thus subscribing to obsOne$.
While your ternary works - I found this article explains a more RxJS driven way of achieving your desired outcome quite nicely: https://rangle.io/blog/rxjs-where-is-the-if-else-operator/

Ok, i found an answer on my own. My solution is to remove iif completely and rely on just ternary operator inside mergeMap. that way its not evaluated after every 'LOCATION_CHANGE' and just if regExp returns true. Thanks for your interest.
export const roomRouteEpic: Epic = (action$, state$) =>
action$.ofType(LOCATION_CHANGE).pipe(
pluck<any, any>('payload'),
mergeMap(payload =>
/^\/room\/\d+$/.test(payload.location.pathname)
? of(
state$.value.rooms.list[payload.location.pathname.split('/')[2]]
? actions.rooms.initRoomEnter()
: actions.rooms.initRoomCreate(),
actions.global.setIsLoading(true),
)
: EMPTY,
),
);

If you use tap operator inside observable creation(because it returns void), it will cause error as below
Error: You provided 'function tapOperatorFunction(source) {
return source.lift(new DoOperator(nextOrObserver, error, complete));
}' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
Remove the tap and put the console in the subscribe().
I have created a stackblitz demo.

Another consideration is that even though Observables and Promises can be used in the same context many times when working with RxJS, their behavior will be different when dealing with iif. As mentioned above, iif conditionally subscribes; it doesn't conditionally execute. I had something like this:
.pipe(
mergeMap((input) =>
iif(() => condition,
functionReturningAPromise(input), // A Promise!
of(null)
)
)
)
This was evaluating the Promise-returning function regardless of the condition because Promises don't need to be subscribed to to run. I fixed it by switching to an if statement (a ternary would have worked as well).

Related

DebounceTime after first value in RxJS

I need a specific behavior that I can't get with the RxJS operators. The closest would be to use DebounceTime only for values entered after the first one, but I can't find a way to do it. I have also tried with ThrottleTime but it is not exactly what I am looking for, since it launches intermediate calls, and I only want one at the beginning that is instantaneous, and another at the end, nothing else.
ThrottleTime
throttleTime(12 ticks, { leading: true, trailing: true })
source: --0--1-----2--3----4--5-6---7------------8-------9---------
throttle interval: --[~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~]--------
output: --0-----------3-----------6-----------7-----------9--------
source_2: --0--------1------------------2--------------3---4---------
throttle interval: --[~~~~~~~~~~~I~~~~~~~~~~~]---[~~~~~~~~~~~]--[~~~~~~~~~~~I~
output_2: --0-----------1---------------2--------------3-----------4-
DebounceTime
debounceTime(500)
source: --0--1--------3------------4-5-6-7-8-9-10-11--13----------------
debounce_interval: -----[~~~~~]--[~~~~~]--------------------------[~~~~~]----------
output: -----------1--------3--------------------------------13---------
What I want
debounceTimeAfterFirst(500) (?)
source: --0--1--------3------------4-5-6-7-8-9-10-11--13----------------
debounce_interval: -----[~~~~~]--[~~~~~]--------------------------[~~~~~]----------
output: --0--------1--3------------4-------------------------13---------
As you see, the debounce time is activated when a new value is entered. If the debounce time passes and any new value has been entered, it stops the listening the debounceTime action and waits to start a new one.
Edit: I forgot to comment that this must be integrated with NgRx’s Effects, so it must be a continuous stream that mustn't be completed. Terminating it would probably cause it to stop listening for dispatched actions.
I would use a throttle combined with a debounceTime:
throttle: from Documentation Emit value on the leading edge of an interval, but suppress new values until durationSelector has completed.
debounceTime: from Documentation Discard emitted values that take less than the specified time between output.
I would use a throttle stream to get the raising edge (the first emission) and then the debounce stream would give us the falling edge.
const source = fromEvent(document.getElementsByTagName('input'), 'keyup').pipe(
pluck('target', 'value')
);
const debounced = source.pipe(
debounceTime(4000),
map((v) => `[d] ${v}`)
);
const effect = merge(
source.pipe(
throttle((val) => debounced),
map((v) => `[t] ${v}`)
),
debounced
);
effect.subscribe(console.log);
See RxJS StackBlitz with the console open to see the values changing.
I prepared the setup to adapt it to NgRx which you mention. The effect I got working is:
#Injectable({ providedIn: 'root' })
export class FooEffects {
switchLight$ = createEffect(() => {
const source = this.actions$.pipe(
ofType('[App] Switch Light'),
pluck('onOrOff'),
share()
);
const debounced = source.pipe(debounceTime(1000), share());
return merge(source.pipe(throttle((val) => debounced)), debounced).pipe(
map((onOrOff) => SetLightStatus({ onOrOff }))
);
});
constructor(private actions$: Actions) {}
}
See NgRx StackBlitz with the proposed solution working in the context of an Angular NgRx application.
share: This operator prevents the downstream paths to simultaneously fetch the data from all the way up the chain, instead they grab it from the point where you place share.
I also tried to adapt #martin's connect() approach. But I don't know how #martin would "reset" the system so that after a long time if a new source value is emitted would not debounce it just in the same manner as you first run it, #martin, feel free to fork it and tweak it to make it work, I'm curious about your approach, which is very smart. I didn't know about connect().
#avicarpio give it a go on your application and let us know how it goes :)
I think you could do it like the following, even though I can't think of any easier solution right now (I'm assuming you're using RxJS 7+ with connect() operator):
connect(shared$ => shared$.pipe(
exhaustMap(value => merge(
of(value),
shared$.pipe(debounceTime(1000)),
).pipe(
take(2),
)),
)),
Live demo: https://stackblitz.com/edit/rxjs-qwoesj?devtoolsheight=60&file=index.ts
connect() will share the source Observable and lets you reuse it in its project function multiple times. I'm using it only to use the source Observable inside another chain.
exhaustMap() will ignore all next notifications until its inner Observable completes. In this case the inner Observable will immediately reemit the current value (of(value)) and then use debounceTime(). Any subsequent emission from source is ignored by exhaustMap() because the inner Observable hasn't completed yet but is also passed to debounceTime(). Then take(2) is used to complete the chain after debounceTime() emits and the whole process can repeat when source emits because exhaustMap() won't ignore the next notification (its inner Observable has completed).
Here's a custom operator that (as far s I can tell) does what you're after.
The two key insights here are:
Use connect so that you can subscribe to the source twice, once to ignore emissions with exhaustMap and another to inspect and debounce emissions with switchMap
Create an internal token so that you know when to exit without a debounced emission. (Insures that from your example above, the 4 is still emitted).
function throttleDebounceTime<T>(interval: number): MonoTypeOperatorFunction<T> {
// Use this token's memory address as a nominal token
const resetToken = {};
return connect(s$ => s$.pipe(
exhaustMap(a => s$.pipe(
startWith(resetToken),
switchMap(b => timer(interval).pipe(mapTo(b))),
take(1),
filter<T>(c => c !== resetToken),
startWith(a)
))
));
}
example:
of(1,2,3,4).pipe(
throttleDebounceTime(500)
).subscribe(console.log);
// 1 [...0.5s wait] 4

rxjs conditional startWith , endWith using iif operator

Still figuring rxjs out and its a great library (have to admit). Given that we have this source and booleans.
let source = [0,1,2,3,4,5,6,7,8,9];
let swEnabled = false;
let ewEnabled = false;
And we would like to conditionally enable startwith and endwith operators based on swEnabled and ewEnabled to the source.....
source.pipe(
iif(() => swEnabled,startWith(1000),),
iif(() => ewEnabled,endWith(1000),),
).subscribe(
(n)=>{
console.log(n);
});
but no joy..... anyone can provide an example of this? Maybe the approach is wrong. Or can you suggest some alternative approach ?
Thanks in advance
You can conditionally build an array of operators as follows:
import { MonoTypeOperatorFunction, of } from 'rxjs';
import { pipeFromArray } from 'rxjs/internal/util/pipe';
import { endWith, startWith } from 'rxjs/operators';
let source = of(0,1,2,3,4,5,6,7,8,9);
let swEnabled = false;
let ewEnabled = false;
let operators: MonoTypeOperatorFunction<number>[] = []
if (swEnabled) {
operators.push(startWith(1000))
}
if (ewEnabled) {
operators.push(endWith(1000))
}
source.pipe(pipeFromArray(operators)).subscribe(e => console.log(e))
There is currently an open issue about not being able to use the spread syntax inside the pipe. If that issue gets fixed, then you will no longer need the pipeFromArray function and the solution can be simplified to source.pipe(...operators).
Re iif: Note that the iif function is an Observable constructor function and not an operator function. That means that you cannot use iif as an operator inside pipe() as you have shown. By observing the RxJx reference page, you can see that there are two sections, amongst others, titled index and operators. Only the functions under operators can be used directly within the pipe(). The functions under index can be used to construct source Observables.
Re startWith and endWith: These two functions are operator functions (meant to be used inside pipe) and not Observables. The iif function expects an Observable as the second and third argument.
I was having the same question, but after trying different strategies I came up with this solution:
someObservable.pipe(
// anyOperator(),
condition ? conditionalOperator() : tap(() => {})
)
In this case, if condition is true, operatorA is added to pipe, if not, the tap(() => {}) is like a "noop" (anyone can mix the condition and parameters to fit any needs).

Operating upon an observable array by flattening/unflattening it, whilst maintaining a stream

Q: can RxJs operators be used to flatten an array, transform items, then unflatten it, whilst maintaining a continuous stream (not completing)?
For the simplified example here: https://stackblitz.com/edit/rxjs-a1791p?file=index.ts
If following the approach:
mergeMap(next => next),
switchMap(next => of(***transforming logic***)),
toArray()
then the observable does not complete, and the values do not come through. A take(1) could be added but this is intended to be a continuous stream.
If using:
mergeMap(next => next),
switchMap(next => of(***transforming logic***)),
scan()
then this works great. However, then each time the source observable emits, the accumulator never resets, so the scan() which is intended to accumulate the values back into an array ends up combining multiple arrays from each pass. Can the accumulator be reset?
Obviously it can be accomplished with:
switchMap(next => of(next.map(***transforming logic***)))
But my real-world example is an awful lot more complicated than this, and is tied into NgRx.
Here would be one approach:
src$.pipe(
mergeMap(
arr => from(arr)
.pipe(
switchMap(item => /* ... */),
toArray(),
)
)
)
For each emitted array, mergeMap will create an inner observable(from(..)). There, from(array) will emit each item separately, allowing you to perform some logic in switchMap. Attaching toArray() at the end will give you an array with the results from switchMap's inner observable.
You don't need to use mergeMap or switchMap here. You would only need those if you are doing something asynchronously. Like if you were taking the input value and creating an observable (ex: to make an http call).
By using of inside of mergeMap, you are essentially starting with an Observable, taking the unpacked value (an array), then turning it back into an Observable.
From your stack blitz:
The reason your first strategy doesn't complete is because toArray() is happening on the level of the source (clicksFromToArrayButton), and that is never going to complete.
If you really wanted to, you could nest it up a level, so that toArray() happens on the level of your array (created with from(), which will complete after all values are emitted).
const transformedMaleNames = maleNames.pipe(
mergeMap(next => from(next).pipe(
map(next => {
const parts = next.name.split(' ');
return { firstName: parts[0], lastName: parts[1] };
}),
toArray()
)
),
);
But... we don't really need to use from to create an observable, just so it can complete, just so toArray() can put it back together for you. We can use the regular map operator instead of mergeMap, along with Array.map():
const transformedMaleNames = maleNames.pipe(
map(nextArray => {
return nextArray.map(next => {
const parts = next.name.split(' ');
return { firstName: parts[0], lastName: parts[1] };
})
})
);
this works, but isn't necessarily utilizing RxJS operators fully?
Well, ya gotta use the right tool for the right job! In this case, you are simply transforming array elements, so Array.map() is perfect for this.
But my real-world example is an awful lot more complicated than this
If you are concerned about the code getting messy, you can just break the transformation logic out into it's own function:
const transformedMaleNames = maleNames.pipe(
map(next => next.map(transformName))
);
function transformName(next) {
const parts = next.name.split(' ');
return { firstName: parts[0], lastName: parts[1] };
}
Here's a working StackBlitz.

Rx.Observable validate items in stream (pass or throw)

Given an Stream within an Observable, I want to validate/check each item. In case one is broken I want to throw an error via Observable.throw, hence break all further processing.
My clunky solution would be
import * as Rx from 'rxjs'
inputStream.mergeMap(item => (isValid(item))
? Rx.Observable.of(item)
: Rx.Observable.throw(new Error("not valid"))
)
This seems ugly, as it constructs for the positive flow a bunch of unnecessary Observables.
Is there a better way to check items in an Observable?
You can use just normal map and throw an exception inside it:
inputStream.map(item => {
if (isValid(item)) {
return item;
}
throw new Error("not valid");
})
If I understand your question well, you can use takeWhile operator to do this. For example;
yourObservable.takeWhile(item => {
//your condition
}).subscribe(i => console.log(i));
It just takes values while your expression is true. When it becomes false, it stops.
You can learn more from here. Also you can check this page out. I hope it helps!

Chained Observable after takeWhile completes is not being called?

I have the following methods which should be called like this:
registerDomain should be called and should return an operationId
After 10 seconds, getOperationDetail should be called passing in the operationId
getOperationDetail should be called every 10 seconds until successful is returned.
Once getOperationDetail finishes, createRecordSets should be called.
Finally, getChangeStatus should be called until it returns INSYNC
If any of the api calls throw an exception, how can I handle the error on the client side?
The following code below calls registerDomain and getOperationDetail, but after getOperationDetail completes, it does not move onto createRecordSets.
registerDomain(domain) {
return this._adminService.registerDomain(domain)
.concatMap(operation => this.getOperationDetail(operation.OperationId))
.concatMap(() => this._adminService.createRecordSets(domain));
}
getOperationDetail(operationId) {
return Observable.interval(10000)
.mergeMap(() => this._adminService.getOperationDetail(operationId))
.takeWhile((info) => info.Status.Value !== 'SUCCESSFUL');
}
createRecordSets(caseWebsiteUrl) {
return this._adminService.createRecordSets(caseWebsiteUrl.Url)
.concatMap(registerId => this.getChangeStatus(registerId));
}
getChangeStatus(registerId) {
return Observable.interval(5000)
.mergeMap(() => this._adminService.getChange(registerId))
.takeWhile((info) => info.ChangeInfo.Status.Value !== 'INSYNC');
}
I updated getOperationDetail to use the first operator:
getOperationDetail(operationId) {
return Observable.interval(3000)
.mergeMap(() => this._adminService.getOperationDetail(operationId))
.first((info) => info.Status.Value === 'SUCCESSFUL')
}
Now it does in fact call createRecordSets, however, after createRecordSets, it continues to call getOperationDetail about 13 times and eventually calls getChangeStatus. The way I was looking at it, I thought it would:
Call getOperationDetail until it receives a SUCCESS.
Call createRecordSets one time.
Call getChangeStatus until it receives an INSYNC
Done.
Why the additional calls?
I changed the registerDomain to look like this:
registerDomain(domain) {
return this._adminService.registerDomain(domain)
.concatMap(operation => this.getOperationDetail(operation.OperationId))
.concatMap((op) => this.createRecordSets(op));
Before I had the .concatMap((op) => this.createRecordSets(op)) chained right after this.getOperationDetail. Once I moved it outside that, it started working as expected. I am unsure why though. Can someone explain?
When takeWhile meets a value that satisfies a specified criteria, it completes the observable without propagating the value. It means that the next chained operator will not receive the value and will not invoke its callback.
Suppose that in your example the first two calls to this._adminService.getOperationDetail(...) result in a non-successful status and the third call succeeds. It means that an observable returned by getOperationDetail() would produce only two info values each of which having non-successful status. And what might be also important, the next chained concatMap operator would invoke its callback per each of those non-successful values, meaning that createRecordSets() would be called twice. I suppose that you might want to avoid that.
I would suggest to use first operator instead:
getOperationDetail(operationId) {
return Observable.interval(10000)
.concatMap(() => this._adminService.getOperationDetail(operationId))
.first(info => info.Status.Value !== 'SUCCESSFUL');
}
This way getOperationDetail() would produce only a single "successful" value as soon as this._adminService.getOperationDetail(operationId) succeeds. The first operator emits the first value of the source observable that matches the specified condition and then completes.
And when it comes to error handling, catch or retry operators might be useful.
Update:
The unexpected behavior you have faced (getOperationDetail() keeps being called after first() completes) seems to be a bug in rxjs. As described in this issue,
every take-ish operator (one that completes earlier than its source Observable), will keep subscribing to source Observable when combined with operator that prolongs subscription (here switchMap).
Both first and takeWhile are examples of such take-ish operators and operators that "prolong" subscription are, for example, switchMap, concatMap and mergeMap. In the following example numbers will be kept logging while inner observable of concatMap is emitting values:
var takeish$ = Rx.Observable.interval(200)
// will keep logging until inner observable of `concatMap` is completed
.do(x => console.log(x))
.takeWhile(x => x < 2);
var source = takeish$
.concatMap(x => Rx.Observable.interval(200).take(10))
.subscribe();
It looks like this can be worked around by turning an observable containing such a take-ish operator into a higher-order observable — in a similar way as you've done:
var takeish$ = Rx.Observable.interval(200)
// will log only 0, 1, 2
.do(x => console.log(x))
.takeWhile(x => x < 2);
var source = Rx.Observable.of(null)
.switchMap(() => takeish$)
.concatMap(x => Rx.Observable.interval(200).take(1))
.subscribe();
Update 2:
It seems that the bug described above still exists as of rxjs version 5.4.2. It affects, for example, whether or not the source observable of the first operator will be unsubscribed when first meets the specified condition. When first operator is immediately followed by concatMap, its source observable will not be unsubscribed and will keep emitting values until inner observable of concatMap completes. In your case it means that this._adminService.getOperationDetail() would keep being called until observable returned by createRecordSets() would have completed.
Here's your example simplified to illustrate the behavior:
function registerDomain() {
return Rx.Observable.of("operation")
.concatMap(() => getOperationDetail()
.concatMap(() => Rx.Observable.interval(200).take(5)));
}
function getOperationDetail() {
return Rx.Observable.interval(100)
// console.log() instead of the actual service call
.do(x => console.log(x))
.first(x => x === 2);
}
registerDomain().subscribe();
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
If we expand the inner observable of the first concatMap operator, we will get the following observable:
Rx.Observable.interval(100)
.do(x => console.log(x))
.first(x => x === 2)
.concatMap(() => Rx.Observable.interval(200).take(5));
Notice that first is immediately followed by concatMap which prevents the source observable of the first operator (i.e. interval(100).do(x => console.log(x)) from being unsubscribed. Values will keep being logged (or in your case, service calls will keep being sent) until the inner observable of concatMap (i.e. interval(200).take(5)) completes.
If we modify the example above and move the second concatMap out of the inner observable of the first concatMap, first will not be chained with it any more and will unsubscribe from the source observable as soon as the condition is satisfied, meaning that interval will stop emitting values and no more numbers will be logged (or no more service requests will be sent):
function registerDomain() {
return Rx.Observable.of("operation")
.concatMap(() => getOperationDetail())
.concatMap(() => Rx.Observable.interval(200).take(5));
}
function getOperationDetail() {
return Rx.Observable.interval(100)
// console.log() instead of the actual service call
.do(x => console.log(x))
.first(x => x === 2);
}
registerDomain().subscribe();
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
Inner observable in such a case can be expanded simply to:
Rx.Observable.interval(100)
.do(x => console.log(x))
.first(x => x === 2)
Notice that first is not anymore followed by concatMap.
It is also worth mentioning that in both cases observable returned by registerDomain() produces exactly the same values and if we move logging from do() operator to subscribe(), the same values will be written to the console in both cases:
registerDomain.subscribe(x => console.log(x));

Resources