In an rxjs stream, I'm using distinctUntilChanged with lodash's isEqual to filter out duplicate values. However it appears to not be working as expected. Take the following code snippet
import { isEqual } from 'lodash-es';
let cachedValue: any;
function testFn(observableVal: Observable<any>) {
return observableVal
.pipe(
distinctUntilChanged(isEqual),
tap(val => {
const equal = isEqual(cachedValue, val);
console.log('"output":', equal, cachedValue, val);
cachedValue = val;
})
)
}
In this example, I would expect that const equal inside the tap function would never === true. I would expect that distinctUntilChanged(isEqual) would filter out any values where isEqual(cachedValue, val) === true --> meaning that const equal === false always. However, console output shows:
"output": false undefined [ContactList]
"output": true [ContactList] [ContactList]
"output": true [ContactList] [ContactList]
"output": true [ContactList] [ContactList]
Do I misunderstand something fundamental about how the distinctUntilChanged() operator works? I've posted a simplified example because the actual rxjs stream is very complex, but I wouldn't expect the complexity to make any difference in so far as const equal should always === false in the tap operator.
I'm just trying to understand what's going on, so any info is appreciated. Thanks!
Update
It should be noted that if I change the code to:
function testFn(observableVal: Observable<any>) {
return observableVal
.pipe(
filter(val => {
const equal = isEqual(cachedValue, val);
cachedValue = val;
return !equal;
}),
tap(val => {
console.log('"output":', val);
})
)
}
Then the filtering works as expected. I was under the impression that distinctUntilChanged(isEqual) was equivalent to:
filter(val => {
const equal = isEqual(cachedValue, val);
cachedValue = val;
return !equal;
})
Am I mistaken / misunderstanding the distinctUntilChanged operator?
I figured it out! Thanks to a comment in an rxjs issue: I had accidently subscribed to the observable multiple times (which shouldn't have happened). The multiple console.log instances were coming from different subscription instances.
In case someone will get here because of similar issue as I had - keep in mind that custom comparator function should return false to pass the distinctUntilChanged operator.
In other words this comparator will allow to pass when no changes in someProp:
distinctUntilChanged((prev, curr) => prev.someProp !== curr.someProp)
and this - only if someProp was changed:
distinctUntilChanged((prev, curr) => prev.someProp === curr.someProp)
Related
My desired behaviour:
Run HTTP request
Immediately look up data in async cache
If cache has the value before HTTP emits - use cache value.
Use HTTP value after it's finally here.
If HTTP responds faster than cache - ignore cache.
So basically I would like to kick off two async processes, one of which is supposed to provide a value quickly but if it doesn't - I want to only use the value from a slower observable which takes precedence anyway.
To expand from my comments: the question is to trigger two observables in parallel and utilize the first emission even if the other observable hasn't emitted yet.
Normally you could use the merge function for it.
However you have a condition ("If HTTP responds faster than cache - ignore cache.") that is not natively fulfilled by the merge function nor by any standard RxJS operators.
But it is easy to write custom operators in RxJS from existing operators. For your case you could customize the filter operator to suit your needs. See here for a brief intro on how to write a custom operator.
export const filterLateCache = () => {
let serverEmitted = false;
return <T>(source: Observable<T>) => {
return source.pipe(
filter((data: any) => {
if (!!data.server) {
serverEmitted = true;
return true;
} else if (!!data.cache) {
if (serverEmitted) {
return false;
} else {
return true;
}
} else {
return false;
}
})
);
};
};
As you can see the boolean flags server and cache in the incoming notification are checked to decide whether the value must be emitted. So you'd need to append the values from the observables with these flags using the map operator.
merge(
server$.pipe(
map((value) => ({
server: true,
value: value,
}))
),
cache$.pipe(
map((value) => ({
cache: true,
value: value,
}))
)
)
.pipe(filterLateCache())
.subscribe({
next: ({ value }) => { // <-- utilize destructuring to ignore boolean flags
// handle response here
},
error: (error: any) => {
// handle errors
}
});
Working example: Stackblitz
Maybe it is worth looking at the raceWith: https://rxjs-dev.firebaseapp.com/api/operators/raceWith
Basically it would look like:
server$.pipe(raceWith(cache$)).subscribe(/*side effect that must be done*/);
The thing missing is that it does not fulfill requirement 4.
I'm learning Rxjs observables and trying to figure out why this following filter isn't working as I expected.
My question is with the following setup, why is the code in the .then() block executed even when classA's var1 value is not true? Shouldn't the filter function only allow true values through to be turned into a promise and the rest gets discarded?
Class A:
var1: boolean;
Class B:
var2$: observable<boolean> = of(classA.var1)
FilterObservableValue(){
this.var2$.pipe(
filter( x => x === true)
)
.toPromise()
.then(() => {
//Why is Code here executed when value of var2$ is false? Shouldn't filter function filter out non true values?
})
.catch(err => {
console.log(err);
});
}
The filter operator is working well, The problem is with the toPromise:
You have to know that it's the natural behavior of Promises. so even if it will not receive any value it will not reject and it will be fulfilled with an undefined value
I have some javascript:
this.mySubscription = someObservable.subscribe((obs: any) => {
this.mySubscription.unsubscribe();
this.mySubscription = undefined;
}
on execution, the console logs the error ERROR TypeError: Cannot read property 'unsubscribe' of undefined.
I wonder why I can not unsubscribe inside the subscribe lambda function. Is there a correct way to do so? I have read a bit about using dummy-subjects and completing them or using takeUntil/takeWhile and other pipe operators workArounds.
What is a correct way/workaround to unsubscribe a subscription inside the subscription's subscribe-function?
I am currently using a dummy subscription like so:
mySubscription: BehaviorSubject<any> = new BehaviorSubject<any>(undefined);
// when I do the subscription:
dummySubscription: BehaviorSubject<any> = new BehaviourSubject<any>(this.mySubscription.getValue());
this.mySubscription = someObservable.subscribe((obs: any) => {
// any work...
dummySubscription.next(obs);
dummySubscription.complete();
dummySubscription = undefined;
}, error => {
dummySubscription.error(error);
});
dummySubscription.subscribe((obs: any) => {
// here the actual work to do when mySubscription emits a value, before it should have been unsubscribed upon
}, err => {
// if errors need be
});
You shouldn't try to unsubscribe in the subscribe function.
You can unsubscribe with operators like take, takeWhile or takeUntil.
take
Use take(n) to unsubscribe after someObservable emits n times.
someObservable.pipe(
take(1)
).subscribe(value => console.log(value));
takeWhile
Use takeWhile to unsubscribe when an emitted value fails a condition.
someObservable.pipe(
takeWhile(value => valueIsSave(value))
).subscribe(value => console.log(value));
valueIsSave(value): boolean {
// return true if the subscription should continue
// return false if you want to unsubscribe on that value
}
takeUntil
Use takeUntil(obs$) to unsubscribe when the observable obs$ emits.
const terminate = new Subject();
someObservable.pipe(
takeUntil(terminate)
).subscribe(value => console.log(value));
unsub() {
terminate.next() // trigger unsubscribe
}
If you make your stream asynchronous, what you're doing should work. For example, this will not work:
const sub = from([1,2,3,4,5,6,7,8,9,10]).subscribe(val => {
console.log(val);
if(val > 5) sub.unsubscribe();
});
but this will work:
const sub2 = from([1,2,3,4,5,6,7,8,9,10]).pipe(
delay(0)
).subscribe(val => {
console.log(val);
if(val > 5) sub2.unsubscribe();
});
Because the JS event loop is fairly predictable (blocks of code are always run to completion), If any part of your stream is asynchronous, then you can be sure that your subscription will be defined before your lambda callback is invoked.
Should you do this?
Probably not. If your code relies on the internal (otherwise hidden) machinations of your language/compiler/interpreter/etc, you've created brittle code and/or code that is hard to maintain. The next developer looking at my code is going to be confused as to why there's a delay(0) - that looks like it shouldn't do anything.
Notice that in subscribe(), your lambda has access to its closure as well as the current stream variable. The takeWhile() operator has access to the same closure and the same stream variables.
from([1,2,3,4,5,6,7,8,9,10]).pipe(
takeWhile(val => {
// add custom logic
return val <= 5;
})
).subscribe(val => {
console.log(val);
});
takeWhile() can to anything that sub = subscribe(... sub.unsubscibe() ... ), and has the added benefit of not requiring you to manage a subscription object and being easier to read/maintain.
Inspired by another answer here and especially this article, https://medium.com/#benlesh/rxjs-dont-unsubscribe-6753ed4fda87, I'd like to suggest takeUntil() with following example:
...
let stop$: Subject<any> = new Subject<any>(); // This is the one which will stop the observable ( unsubscribe a like mechanism )
obs$
.pipe(
takeUntil(stop$)
)
.subscribe(res => {
if ( res.something === true ) {
// This next to lines will cause the subscribe to stop
stop$.next();
stop$.complete();
}
});
...
And I'd like to quote sentence RxJS: Don’t Unsubscribe from those article title mentioned above :).
To access state in an ngrx effect, we can use withLatestFrom like this (taken from the documentation https://ngrx.io/guide/effects):
this.actions$.pipe(
ofType(CollectionApiActions.addBookSuccess),
concatMap(action => of(action).pipe(
withLatestFrom(this.store.pipe(select(fromBooks.getCollectionBookIds)))
)),
tap(([action, bookCollection]) => {
if (bookCollection.length === 1) {
window.alert('Congrats on adding your first book!');
} else {
window.alert('You have added book number ' + bookCollection.length);
}
})
)
This effect, like many effects in my application, does not actually access the action parameter from the list that we get by using withLatestFrom but is only interested in the bookCollection. So I was wondering, why not simply do this:
function getLatestFrom<T, R>(observable: Observable<T>) {
return concatMap((action: R) => of(action).pipe(
withLatestFrom(observable),
map(([, value]) => value)
))
}
and replace the above snippet with
this.actions$.pipe(
ofType(CollectionApiActions.addBookSuccess),
getLatestFrom(this.store.pipe(select(fromBooks.getCollectionBookIds))),
tap(bookCollection => {
if (bookCollection.length === 1) {
window.alert('Congrats on adding your first book!');
} else {
window.alert('You have added book number ' + bookCollection.length);
}
})
)
This looks much cleaner to me, but I haven't found anything like this anywhere, so I was wondering if there is a reason against doing this?
but is only interested in the bookCollection
rather than using withLatestFrom operator which returns an array of results from both source and input Observables, you might be better with switchMapTo which returns result from input observable only, as demonstrated below:
SwitchMapTo
this.actions$.pipe(
ofType(CollectionApiActions.addBookSuccess),
switchMapTo(this.store.pipe(select(fromBooks.getCollectionBookIds), first())),
tap(bookCollection => {})
)
VS
WithLatestFrom
this.actions$.pipe(
ofType(CollectionApiActions.addBookSuccess),
withLatestFrom(this.store.pipe(select(fromBooks.getCollectionBookIds))),
tap(([action, bookCollection]) => {})
)
I want to resolve an observable but I don't want the return value to replace the previous value in the pipe. Is there any asynchronous tap()? I need an operator like a switchMap but I want to ignore the return.
of(1).pipe(switchMap(() => of(2))).subscribe(console.log); // expected: 1
I could create a custom operator but sure there's something built-in in rxjs.
I ended up with this custom operator. It is like tap but resolves observables (and should be updated to also support promises).
export function switchTap<T, R>(next: (x: T) => Observable<R>): MonoTypeOperatorFunction<T>;
export function switchTap<R>(observable: Observable<R>): MonoTypeOperatorFunction<R>;
export function switchTap<T, R>(
arg: Observable<T> | ((x: T) => Observable<R>)
): MonoTypeOperatorFunction<T> {
const next: (x: any) => Observable<T | R> =
typeof arg === 'function' ? arg : (x: any): Observable<T> => arg;
return switchMap<T, T>(value => next(value).pipe(ignoreElements(), concat(of(value))));
}
Usage:
of(1).pipe(switchTap(of(2))).subscribe(console.log) // 1
or with a function:
of(1)
.pipe(
switchTap(value => {
console.log(value); // value: 1
return of(value + 1);
})
)
.subscribe(console.log); // 1
If you just want to simply ignore the values of the subscribe, then just don't pass in any arguments in the subscribe callback:
of(1).pipe(switchMap(() => of(2))).subscribe(()=>{
console.log('no arguments')
});
If you however want to retain the values of the first observable, things can get tricky. One way is to use Subject to retain the value:
//create a BehaviorSubject
var cache = new BehaviorSubject<any>(0);
of(1).pipe(switchMap((first) => {
cache.next(first);
return of(2);
})).subscribe(() => {
console.log(cache.value) //gives 1
});
Or you can use .map() to alter the values. This is kind of hacky and the code is harder to maintain:
of(1).pipe(switchMap((first) => {
return of(2).map(() => first);
})).subscribe((second) => {
console.log(second) //gives 1 because the values was mapped
});
I do it like so
of(2).pipe(
switchMap( num => this.doSmtg(num), num => num)
).subscribe(num => console.log(num)); // 2
Second param of switchmap receives two value the one passed to this.doSmtg and the value returned by doSmtg(num)'s observable.
For anyone new having the same problem I would advise using the resultSelector parameter supported by switchMap and other RxJS mapping operators.
Example:
switchMap(1 => of(2), (one, two) => one)
For further reading: https://www.learnrxjs.io/operators/transformation/mergemap.html
I think you could use delayWhen operator to achieve a similar functionality.
of(1)
.pipe(
delayWhen(value => {
console.log(value); // value: 1
return of(value + 1);
})
).subscribe(console.log); // 1