How to compare values before pushing to subject - single data source
The crux of Ben Lesh's answer is that using .value
is a sign that you're not using RxJS to it's best ability.
If you're using getValue() you're doing something imperative in declarative paradigm.
To a lesser extent, that's true with Subjects in general. They're typically used for either of two purposes. Multicasting, or bridging between imperative and declarative code.
All you need here is the multi-casting component. In most cases, you can use a operator (they use subjects under the hood) to do that for you.
A lot of your song and dance here is to implement distinctUntilChanged
declaratively. In so doing, you have created a version that is both much slower (shouldn't matter here) and much harder to maintain (should matter here).
Here is how I might refactor your code (using shareReplay
& distinctUntilChanged
) to be a bit more in line with dogmatic RxJS.
interface Something {
length: number
}
class SomeService implements OnInit, OnDestroy {
/* Errors are "false", Data without a length is "undefined", and
everything else is "something". I wouldn't reccomend this,
but as an example, sure.
*/
private dataOb$: Observable<(Something | Boolean | undefined)[]>
private pollingSubscription: Subscription;
constructor(private readonly httpClient: HttpClient) {
}
ngOnInit() {
this.dataOb$ = timer(0,1000).pipe(
concatMap(() => this.getDataFromBackend()),
distinctUntilChanged(Lodash.isEqual),
shareReplay(1) // multicasting
)
// This service is effectively "using" itself. This means
// the polling continues even if nobody else is listening.
this.pollingSubscription = this.getDataObs().subscribe()
}
private getDataFromBackend(): Observable<(Something | Boolean | undefined)[]> {
// This is a bizzaar function, but I assume it's just as an example
return this.httpClient.get(url, options).pipe(
map((response: Something[]) => {
if (response?.length > 0) {
return response;
}
return undefined;
}),
catchError(() => of(false))
)
}
// I changed this from a private method
getDataObs(): Observable<(Something | Boolean | undefined)[]> {
return this.dataOb$
}
ngOnDestroy() {
this.pollingSubscription.unsubscribe();
}
}
A quick aside:
Array<int>
is the same as int[]
andArray<int|boolean>
is the same as (<int|boolean>)[]
Update
If you want to (for example) ignore errors and empty emissions:
private getDataFromBackend(): Observable<Something[]> {
return this.httpClient.get<Something[]>(url, options).pipe(
filter(response => response?.length > 0),
catchError(() => EMPTY)
)
}