distinctUntilChanged with ngrx - rxjs

I'm struggling with the code sample below. I've tried having compareFilters always return true, and always return false, and either way I am getting lots of "*******" in my logs - at least one should have completely cut off supply I expected? What am I missing?
this.filters = this.store
.select(state => state.filters)
// This line needed as otherwise change in geolocation triggers state to be
// resent, overwriting any changes user has made on filters menu
.pipe(
distinctUntilChanged(compareFilters),
tap((filters: Filters) => {
console.log("*****", filters);
this.location.pushState(
{},
"",
"/recommendations/" + toUrl(filters)
);
})
);
More generally, what I see in my components is that no matter which part of the state has changed ngOnChanges reports changes in all props every time

Hmm I don't think so. I wrote the quick example on StackBliz and it work as expected. Only 1 is printed. Are you sure that the ngrx store does not emit all thous events? Because your rxjs code is correct.
import { Observable, of } from 'rxjs';
import { distinctUntilChanged, tap } from 'rxjs/operators';
of("1", "2", "3", "4" ,"5")
.pipe(
distinctUntilChanged(coperator),
tap(t => console.log(t))
)
.subscribe()
function coperator(t){
return true;
}
May be the problem is with the call to this.location.pushState you call in your tap method something that modifies your store so that generate a new event. I don't know but it wont hurt to bounce some ideas.

Related

rxjs: what is the best way to dynamically add values to an observable stream?

As part of learning rxjs ive been using create methods of, from, interval etc. to test throttle and deboucne etc ive been creating streams using fromevent.
now i have a real use case and i need to dynamically add values into an empty observable stream. i cant find any examples on how best to do this NOT using the creation methods above. Presently Im using a BehaviourSubject to dynamically add items to a stream using next(). Is this the best/preferred way of DYNAMICALLY adding new items to a stream?
e.g.
import { BehaviorSubject, timer } from 'rxjs';
import { tap, mapTo, concatMap, } from 'rxjs/operators';
const subject = new BehaviorSubject(1);
const example = subject.pipe(
concatMap(ev => timer(200).pipe(mapTo(ev))),
tap((ev) => console.log(ev))
)
example.subscribe();
// add a flurry of values dynamically
subject.next(2);
subject.next(3);
subject.next(4);
// some time later add some more
setTimeout(function(){
subject.next(5);
subject.next(6);
subject.next(7);
}, 5000);
https://stackblitz.com/edit/rxjs-behaviorsubject-simpleexample-gyrtw8?file=index.ts
Thanks
If you have a veeery custom logic of adding values that should be emitted in an Observable, you can create your own (instead of using fromEvent, of, from, ...):
const myObservable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.next(5);
setTimeout(() => {
subscriber.next(6);
}, 2000);
}, 1000);
});
However, the rxjs's creation functions should cover 99% of your needs.
The code above can be also written like:
concat(
of(1,2,3),
of(4,5).pipe(
delay(1000)
),
of(6).pipe(
delay(2000)
)
)
UPD: About Subjects
Subject is also an Observable so in your case using Subject is applicable but might not be the best option. The idea of a Subject is that there can be more than one subscriber (the one who uses the values from subject) but I'm not sure that it's your case (by the way - you can provide your real-life example to help us understand what you want to achieve)

Tap after observable has been subscribed

Is there a way to add more operations to an observable that has already been subscribed? I tried the below code, which doesn't work, as the One more tap after subscribe part is not executed (code is here)
import { of, timer } from 'rxjs';
import { tap, map, take } from 'rxjs/operators';
const source = timer(1000, 1000);
//transparently log values from source with 'do'
const example = source.pipe(
take(3),
tap(val => console.log(`BEFORE MAP: ${val}`)),
map(val => val + 10),
tap(val => console.log(`AFTER MAP: ${val}`))
);
//'do' does not transform values
//output: 11...12...13...14...15
const subscribe = example.subscribe(val => console.log(val));
example.pipe(
tap(val => console.log(`One more tap after subscribe: ${val}`))
);
The use cas I have in mind is where for example I make an http call, and more than one service needs to be updated with the reponse of the call.
I will take this as what you ultimately want to achieve
The use cas I have in mind is where for example I make an http call, and more than one service needs to be updated with the reponse of the call.
const onExampleCalled=new Subject();
// listen to example called
onExampleCalled.subscribe(console.log)
example.pipe(tap(result=>onExampleCalled.next(result)).subscribe()
I am not quite sure what you try to achieve, but the pipe() function does not alter the source Observable. It just outputs a new Observable that results from the old one and the operators you aplied in the pipe(). Therefor your last line of code is like writing
5;
Meaning you set a value as a statement. Maybe you could reassign your Observable to itself after Transformation (although I am sure, that it would look quite ugly and will be hard to understand for others and should therefor be avoided).
example = example.pipe(
tap(val => console.log(`One more tap after subscribe: ${val}`))
);
Maybe you should go more into detail what you specific usecase is, so that we can find a cleaner solution.

Rxjs refCount callback to cleanup once every subscribers have unsubscribed?

I've got an observable which is not long lived (http request).
I'm using publishReplay(1) and refCount() so that when there an attempt to access it at the same time, it'll return the same value without triggering the http call again.
But if all the subscriptions are unsubscribed, I need to make some cleanup.
I can't use finalize because:
if I use it before publishReplay then it get closed once the http request is done
if I use it after refCount it'll be run as soon as one observable unsubscribe (instead of when all have unsubscribed)
So basically what I'd like would be to pass a callback to refCount and call that callback when the number of subscriptions reaches 0. But it doesn't work like that. Is there any way to be "warned" when all the subscribers have unsubscribed?
The simplest way I can think of right now would be to create a custom operator that'd pretty much extend refCount to add a callback.
Any better thoughts? I'm pretty sure that there's a better way of doing that.
Thanks!
I am not gonna lie, I was happy to find out I wasn't the only one looking for something like that. There is one another person.
I ended up doing something like that:
import { Observable } from 'rxjs';
export function tapTeardown(teardownLogic: () => void) {
return <T>(source: Observable<T>): Observable<T> =>
new Observable<T>((observer) => {
const subscription = source.subscribe(observer);
return () => {
subscription.unsubscribe();
teardownLogic();
};
});
}
And you use it like:
const augmented = connection.pipe(
tapTeardown(() => /* SOME TEARDOWN LOGIC */),
shareReplay({ bufferSize: 1, refCount: true }),
);
I've tried it and it seems to work correctly.
Here's how it's used:
import { of, timer } from 'rxjs';
import { map, publishReplay, take } from 'rxjs/operators';
import { refCountCb } from './refCountCb';
const source = timer(2000, 10000).pipe(
map(x => `Hello ${x}!`),
publishReplay(1),
refCountCb(() => console.log('MAIN CLOSED'))
);
source.pipe(take(1)).subscribe(x => console.log(x));
source.pipe(take(1)).subscribe(x => console.log(x));
Output:
Hello 0!
Hello 0!
MAIN CLOSED
I've built the custom refCountCb operator based on the source of refCount. It's basically just adding a callback so I won't copy paste the whole code here but it's available on the stackblitz.
Full demo: https://stackblitz.com/edit/rxjs-h7dbfc?file=index.ts
If you have any other idea please share it, I'd be glad to discover different solutions!

RxJS 6: Why calling value on BehaviorSubject is a bad thing? (according to no-subject-value lint rule) [duplicate]

I have an Angular 2 service:
import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class SessionStorage extends Storage {
private _isLoggedInSource = new Subject<boolean>();
isLoggedIn = this._isLoggedInSource.asObservable();
constructor() {
super('session');
}
setIsLoggedIn(value: boolean) {
this.setItem('_isLoggedIn', value, () => {
this._isLoggedInSource.next(value);
});
}
}
Everything works great. But I have another component which doesn't need to subscribe, it just needs to get the current value of isLoggedIn at a certain point in time. How can I do this?
A Subject or Observable doesn't have a current value. When a value is emitted, it is passed to subscribers and the Observable is done with it.
If you want to have a current value, use BehaviorSubject which is designed for exactly that purpose. BehaviorSubject keeps the last emitted value and emits it immediately to new subscribers.
It also has a method getValue() to get the current value.
The only way you should be getting values "out of" an Observable/Subject is with subscribe!
If you're using getValue() you're doing something imperative in declarative paradigm. It's there as an escape hatch, but 99.9% of the time you should NOT use getValue(). There are a few interesting things that getValue() will do: It will throw an error if the subject has been unsubscribed, it will prevent you from getting a value if the subject is dead because it's errored, etc. But, again, it's there as an escape hatch for rare circumstances.
There are several ways of getting the latest value from a Subject or Observable in a "Rx-y" way:
Using BehaviorSubject: But actually subscribing to it. When you first subscribe to BehaviorSubject it will synchronously send the previous value it received or was initialized with.
Using a ReplaySubject(N): This will cache N values and replay them to new subscribers.
A.withLatestFrom(B): Use this operator to get the most recent value from observable B when observable A emits. Will give you both values in an array [a, b].
A.combineLatest(B): Use this operator to get the most recent values from A and B every time either A or B emits. Will give you both values in an array.
shareReplay(): Makes an Observable multicast through a ReplaySubject, but allows you to retry the observable on error. (Basically it gives you that promise-y caching behavior).
publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject), etc: Other operators that leverage BehaviorSubject and ReplaySubject. Different flavors of the same thing, they basically multicast the source observable by funneling all notifications through a subject. You need to call connect() to subscribe to the source with the subject.
I had similar situation where late subscribers subscribe to the Subject after its value arrived.
I found ReplaySubject which is similar to BehaviorSubject works like a charm in this case.
And here is a link to better explanation: http://reactivex.io/rxjs/manual/overview.html#replaysubject
const observable = of('response')
function hasValue(value: any) {
return value !== null && value !== undefined;
}
function getValue<T>(observable: Observable<T>): Promise<T> {
return observable
.pipe(
filter(hasValue),
first()
)
.toPromise();
}
const result = await getValue(observable)
// Do the logic with the result
// .................
// .................
// .................
You can check the full article on how to implement it from here.
https://www.imkrish.com/blog/development/simple-way-get-value-from-observable
I encountered the same problem in child components where initially it would have to have the current value of the Subject, then subscribe to the Subject to listen to changes. I just maintain the current value in the Service so it is available for components to access, e.g. :
import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class SessionStorage extends Storage {
isLoggedIn: boolean;
private _isLoggedInSource = new Subject<boolean>();
isLoggedIn = this._isLoggedInSource.asObservable();
constructor() {
super('session');
this.currIsLoggedIn = false;
}
setIsLoggedIn(value: boolean) {
this.setItem('_isLoggedIn', value, () => {
this._isLoggedInSource.next(value);
});
this.isLoggedIn = value;
}
}
A component that needs the current value could just then access it from the service, i.e,:
sessionStorage.isLoggedIn
Not sure if this is the right practice :)
A similar looking answer was downvoted. But I think I can justify what I'm suggesting here for limited cases.
While it's true that an observable doesn't have a current value, very often it will have an immediately available value. For example with redux / flux / akita stores you may request data from a central store, based on a number of observables and that value will generally be immediately available.
If this is the case then when you subscribe, the value will come back immediately.
So let's say you had a call to a service, and on completion you want to get the latest value of something from your store, that potentially might not emit:
You might try to do this (and you should as much as possible keep things 'inside pipes'):
serviceCallResponse$.pipe(withLatestFrom(store$.select(x => x.customer)))
.subscribe(([ serviceCallResponse, customer] => {
// we have serviceCallResponse and customer
});
The problem with this is that it will block until the secondary observable emits a value, which potentially could be never.
I found myself recently needing to evaluate an observable only if a value was immediately available, and more importantly I needed to be able to detect if it wasn't. I ended up doing this:
serviceCallResponse$.pipe()
.subscribe(serviceCallResponse => {
// immediately try to subscribe to get the 'available' value
// note: immediately unsubscribe afterward to 'cancel' if needed
let customer = undefined;
// whatever the secondary observable is
const secondary$ = store$.select(x => x.customer);
// subscribe to it, and assign to closure scope
sub = secondary$.pipe(take(1)).subscribe(_customer => customer = _customer);
sub.unsubscribe();
// if there's a delay or customer isn't available the value won't have been set before we get here
if (customer === undefined)
{
// handle, or ignore as needed
return throwError('Customer was not immediately available');
}
});
Note that for all of the above I'm using subscribe to get the value (as #Ben discusses). Not using a .value property, even if I had a BehaviorSubject.
Although it may sound overkill, this is just another "possible" solution to keep Observable type and reduce boilerplate...
You could always create an extension getter to get the current value of an Observable.
To do this you would need to extend the Observable<T> interface in a global.d.ts typings declaration file. Then implement the extension getter in a observable.extension.ts file and finally include both typings and extension file to your application.
You can refer to this StackOverflow Answer to know how to include the extensions into your Angular application.
// global.d.ts
declare module 'rxjs' {
interface Observable<T> {
/**
* _Extension Method_ - Returns current value of an Observable.
* Value is retrieved using _first()_ operator to avoid the need to unsubscribe.
*/
value: Observable<T>;
}
}
// observable.extension.ts
Object.defineProperty(Observable.prototype, 'value', {
get <T>(this: Observable<T>): Observable<T> {
return this.pipe(
filter(value => value !== null && value !== undefined),
first());
},
});
// using the extension getter example
this.myObservable$.value
.subscribe(value => {
// whatever code you need...
});
There are two ways you can achieve this.
BehaviorSubject has a method getValue() which you can get the value in a specific point of time.
You can subscribe directly with the BehaviorSubject and you may pass the subscribed value to a class member, field or property.
I wouldn't recommend both approaches.
In the first approach, it's a convenient method you can get the value anytime, you may refer to this as the current snapshot at that point of time. Problem with this is you can introduce race conditions in your code, you may invoke this method in many different places and in different timing which is hard to debug.
The second approach is what most developers employ when they want a raw value upon subscription, you can track the subscription and when do you exactly unsubscribe to avoid further memory leak, you may use this if you're really desperate to bind it to a variable and there's no other ways to interface it.
I would recommend, looking again at your use cases, where do you use it? For example you want to determine if the user is logged in or not when you call any API, you can combine it other observables:
const data$ = apiRequestCall$().pipe(
// Latest snapshot from BehaviorSubject.
withLatestFrom(isLoggedIn),
// Allow call only if logged in.
filter(([request, loggedIn]) => loggedIn)
// Do something else..
);
With this, you may use it directly to the UI by piping data$ | async in case of angular.
A subscription can be created, then after taking the first emitted item, destroyed. In the example below, pipe() is a function that uses an Observable as its input and returns another Observable as its output, while not modifying the first observable.
Sample created with Angular 8.1.0 packages "rxjs": "6.5.3", "rxjs-observable": "0.0.7"
ngOnInit() {
...
// If loading with previously saved value
if (this.controlValue) {
// Take says once you have 1, then close the subscription
this.selectList.pipe(take(1)).subscribe(x => {
let opt = x.find(y => y.value === this.controlValue);
this.updateValue(opt);
});
}
}
You could store the last emitted value separately from the Observable. Then read it when needed.
let lastValue: number;
const subscription = new Service().start();
subscription
.subscribe((data) => {
lastValue = data;
}
);
The best way to do this is using Behaviur Subject, here is an example:
var sub = new rxjs.BehaviorSubject([0, 1])
sub.next([2, 3])
setTimeout(() => {sub.next([4, 5])}, 1500)
sub.subscribe(a => console.log(a)) //2, 3 (current value) -> wait 2 sec -> 4, 5
Another approach, If you want / can to use async await (has to be inside of an async functions) you can do this with modern Rxjs:
async myFunction () {
const currentValue = await firstValueFrom(
of(0).pipe(
withLatestFrom(this.yourObservable$),
map((tuple) => tuple[1]),
take(1)
)
);
// do stuff with current value
}
This will emit a value "Right away" because of withLatestFrom, and then will resolve the promise.

RxJS: Is there an no-op observable?

I have an action that will then trigger an ajax request.
If the action fails for some reason, I want to do nothing. Instead of creating a blank action that just returns the previous state, is there a no-op function I can execute?
export default function fetchMeetups(action$) {
return action$.ofType(statusActions.START_APP)
.mergeMap(action =>
ajax.getJSON(`${config.API_BASE_URL}/api/v1/meetups`)
.map(meetups => calendarActions.meetupsReceived(meetups))
)
.catch(error => Observable.noop())
};
I already have the meetups saved from the last time the app was open (using redux-persist), so if the api request fails I just want it to do nothing.
Is this possible?
I found this from Rxjs but I have no clue how to use it: https://xgrommx.github.io/rx-book/content/helpers/noop.html
Heads up: that link to xgrommx references RxJS v4, not v5 or v6. noop is also just a function that does nothing--not an Observable which emits nothing, which is what I believe you're looking for.
That said, I would highly discourage against swallowing errors completely like this. It can make debugging this and other things very very hard later. I would at least log the error message out.
v5 comes with Observable.empty() or import { empty } from 'rxjs/observable/empty'; which produces an Observable that will emit nothing and just immediately complete.
However, there are some other subtleties you probably will run into next. If you let the ajax error propagate up to the outer operator chain, outside of the mergeMap, your Epic will not longer be listening for future actions! Instead, you'll want to catch errors as early as possible, in this case by placing the catch inside the mergeMap. We often call this "isolating our observer chains"
export default function fetchMeetups(action$) {
return action$.ofType(statusActions.START_APP)
.mergeMap(action =>
ajax.getJSON(`${config.API_BASE_URL}/api/v1/meetups`)
.map(meetups => calendarActions.meetupsReceived(meetups))
.catch(e => {
console.error(e);
return Observable.empty();
})
);
};
Now, whenever the ajax (or the map operation) errors, we're catching that error before it propagates out and instead switching to our empty Observable which will complete immediately so the inner chain is now "done" but our Epic will continue to listen for future actions.
UPDATE:
In v6 empty() is imported from the root import { empty } from 'rxjs'; or it is also available as a singleton import { EMPTY } from 'rxjs';, which can be used as-is, you don't call it like you would empty(). It can be reused because Observables are lazy and act like a factory anyway so empty() was redundant.
import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
// etc
source$.pipe(
catchError(e => {
console.error(e);
return EMPTY; // it's not a function, use it as-is.
})
);
As rxjs also accepts arrays, you can simple provide an empty array when you don't want to emit anything
...
.catch(error => return [];)

Resources