What is the purpose of RxJs Subscription and unsubscribe when it not needed? - rxjs

I have seen the following code in an angular application. In the code, a Subscription is being used to subscribe and unsubscribe. I don't understand why a Subscription is used. Is there any benefit in using this pattern?
public routeChangeSub$: Subscription;
this.routeChangeSub$ = this.route.firstChild.paramMap
.subscribe((map: ParamMap) =>
this.getRouteParams(map));
this.routeChangeSub$.unsubscribe();
getRouteParams(map: ParamMap): number {
const characterId = map.get('id');
let id: number = null;
if (characterId) {
this.character$ = this.store.pipe(
select(selectCharacterById, { id: characterId })
);
id = parseInt(characterId, 10);
}
return id;
}
Update:
How this can be different from
this.route.firstChild.paramMap
.subscribe((map: ParamMap) =>
this.getRouteParams(map));
getRouteParams(map: ParamMap): number {
const characterId = map.get('id');
let id: number = null;
if (characterId) {
this.character$ = this.store.pipe(
select(selectCharacterById, { id: characterId })
);
id = parseInt(characterId, 10);
}
return id;

An observable (which is the type of route.firstChild.paramMap) will not emit anything unless something is subscribed to it.
In this file, the author explicitly subscribes to paramMap to trigger a state change by calling getRouteParams(). Then they immediately unsubscribe. If they didn't unsubscribe, the subscription will continue to run which may cause state issues and memory leaks.
A far simpler solution is to use the take(1) operator. This will take an emitted value (the 1 is the number of values to take) then will send a complete signal to the subscription. This causes the subscription to automatically unsubscribe.
this.route.firstChild.paramMap.pipe(
take(1),
tap(map=>this.getRouteParams(map))
).subscribe();
Because we're using take(), we don't need to assign the subscription to a property. The observable will emit one value, and will invoke getRouteParams() before unsubscribing.
Note: If you're not aware, tap() is the operator to use if you want to apply an effect to any state property outside of the observable.

Related

Angular 11 finalize is not called when the subject type value emitted is null and not processed in switchmap

I have a subject which emits a string value and the code is as below: when the components get initialized, the subjectTypeSubject is null. But there is another method in a component get subscribed to this observable where i set isLoading to true. Because the finalize is not getting called, the loading is always set to true. How to make it work so it gets completed when the value is null as well.
private subjectTypeSubject = new BehaviorSubject<string>(null);
private getPage() {
this.subjectTypeSubject.pipe(
filter((selectedSubjectType) => {
console.log('subject type', selectedSubjectType); //first time it comes as null. so it wont go inside switchmap.
return selectedSubjectType && selectedSubjectType !== '';
}),
switchMap((selectedSubjectType) => {
return this.customListsService
.getCustomListItemsByTypeName()
}),
map((customItemsData) => {
return customItemsData
})
);
}
private _getPage(pageNumber: number, search: string) {
this.loading = true;
this._pageSubscription = this.getPage({
pageSize: this._config.pageSize,
pageNumber,
search
})
.pipe(finalize(() => (this.loading = false))) //this is not called
.subscribe((p) => {
this._currentPage = p.pageNumber;
this.options = p.options;
this._allLoaded = p.isLast;
this.loading = false;
});
}
Adding a takeWhile() instead of filter worked for me. If there is any other better solution. please let me know. thanks
BehaviorSubject doesn't complete unless you complete it
There are multiple ways to call complete an observable in a pipe. take, takeWhile and takeUntil are some of them. Calling .complete on the BehaviorSubject is also an option.
But you should ask yourself: is this really what you want to achieve here? After completion it's not possible to pass any data to the subscription, even if the initial BehaviorSubject emits a new value.
One thing that this strange about your code: it should not work at all. In getPage() you are creating a new observable (by piping the BehaviorSubject), but you are not returning it. Therefore it should return undefined. It‘s also a little bit odd that you are using pipe in a function call. You should either declare the pipe during initialization or directly subscribe to a newly created observable.

How to retrieve last value passed to next() of an Rxjs Observable outside the observer

I have a class with more than 30 observable attributes. Each time my server receives a payload containing these 30 attributes I call the next() method for all the corresponding attributes of the instance, so far so good.
The problem is that, sometimes, I have to check for an attribute's value, outside the scope of the observer that subscribed to that observable attribute.
What comes to mind is that I have to have duplicate attributes for everything, one is the observable and the other one is a stateful attribute to save the arriving values for later consumption.
Is there some way to avoid this with a method like: Observable.getCurrentValue()?
As requested, some example code
class Example {
public subjects = {
a1: new Subject<any>(),
a2: new Subject<any>(),
a3: new Subject<any>(),
a4: new Subject<any>(),
a5: new Subject<any>()
}
public treatPayload(data: any) {
for (const prop in data) {
if (data.hasOwnProperty(prop) && prop in this.subjects){
Reflect.get(this.subjects, prop).next(data[prop])
}
}
}
public test() {
const a1_observable = this.subjects.a1.asObservable()
const a2_observable = this.subjects.a2.asObservable()
const example_payload_1 = {
a1: "first",
a2: "second",
a10: "useless"
}
const example_payload_2 = {
a1: "first-second",
a2: "second-second",
a10: "useless-second"
}
a1_observable.subscribe((a1_new_value: any) => {
const i_also_want_the_last_value_emitted_by_a2 = a2_observable.last_value() // of course, this doesn't exist
console.log(a1_new_value)
console.log(i_also_want_the_last_value_emitted_by_a2)
})
this.treatPayload(example_payload_1)
this.treatPayload(example_payload_2)
}
}
So, is there a way to retrieve the correct value of i_also_want_the_last_value_emitted_by_a2 without a pipe operator? I think it would be a problem to emit all values I could possibly use in a subscriber within a pipe of the a2_observable.
You could use BehaviorSubject.value, where you could store your server data.

RxJS test equality of two streams regardless of order

RxJS provides the sequenceEqual operator to compare two streams in order. How would one go about testing equality of two streams regardless of order?
Pseudocode:
//how do we implement sequenceEqualUnordered?
from([1,2,3]).pipe(sequenceEqualUnordered(from([3,2,1]))).subscribe((eq) =>
console.log("Eq should be true because both observables contain the same values")
)
In my particular use case I need to wait until a certain set of values has been emitted or error but I don't care what order they're emitted in. I just care that each value of interest is emitted once.
Here's my solution:
import { Observable, OperatorFunction, Subscription } from 'rxjs';
export function sequenceEqualUnordered<T>(compareTo: Observable<T>, comparator?: (a: T, b: T) => number): OperatorFunction<T, boolean> {
return (source: Observable<T>) => new Observable<boolean>(observer => {
const sourceValues: T[] = [];
const destinationValues: T[] = [];
let sourceCompleted = false;
let destinationCompleted = false;
function onComplete() {
if (sourceCompleted && destinationCompleted) {
if (sourceValues.length !== destinationValues.length) {
emit(false);
return;
}
sourceValues.sort(comparator);
destinationValues.sort(comparator);
emit(JSON.stringify(sourceValues) === JSON.stringify(destinationValues));
}
}
function emit(value: boolean) {
observer.next(value);
observer.complete();
}
const subscriptions = new Subscription();
subscriptions.add(source.subscribe({
next: next => sourceValues.push(next),
error: error => observer.error(error),
complete: () => {
sourceCompleted = true;
onComplete();
}
}));
subscriptions.add(compareTo.subscribe({
next: next => destinationValues.push(next),
error: error => observer.error(error),
complete: () => {
destinationCompleted = true;
onComplete();
}
}));
return () => subscriptions.unsubscribe();
});
}
As many of RxJS operators have some input parameters and as all of them return functions, sequenceEqualUnordered also has some input parameter (mostly the same as Rx's sequenceEqual operator) and it returns a function. And this returned function has the Observable<T> as the source type, and has Observable<boolean> as the return type.
Creating a new Observable that will emit boolean values is exactly what you need. You'd basically want to collect all the values from both source and compareTo Observables (and store them to sourceValues and destinationValues arrays). To do this, you need to subscribe to both source and compareTo Observables. But, to be able to handle subscriptions, a subscriptions object has to be created. When creating a new subscriptions to source and compareTo, just add those subscriptions to subscriptions object.
When subscribing to any of them, collect emitted values to appropriate sourceValues or destinationValues arrays in next handlers. Should any errors happen, propagate them to the observer in error handlers. And in complete handlers, set the appropriate sourceCompleted or destinationCompleted flags to indicate which Observable has completed.
Then, in onComplete check if both of them have completed, and if they all are, compare the emitted values and emit appropriate boolean value. If sourceValues and destinationValues arrays don't have the same lengths, they can't equal the same, so emit false. After that, basically sort the arrays and then compare the two.
When emitting, emit both the value and complete notification.
Also, the return value of function passed to the new Observable<boolean> should be the unsubscribe function. Basically, when someone unsubscribes from new Observable<boolean>, it should also unsubscribe from both source and compareTo Observables and this is done by calling () => subscriptions.unsubscribe(). subscriptions.unsubscribe() will unsubscribe from all subscriptions that are added to it.
TBH, I haven't wrote any tests for this operator, so I'm not entirely sure that I have covered all edge cases.
My first idea. Use toArray on both then zip them together finally sort and compare results?

Nested observables / subscribe within observable

Having read that you should never subscribe within another observable, I am having major difficulties understanding how to properly handle nested observables.
For every Candidate emitted, I want to match against multiple regular expressions, provided as an observable getPatterns$(). If one is found, it will be attached to the Candidate object.
class Candidate {
public name: string;
public matchingRegularExpression: RegExp;
}
listOfCandidates = [new Candidate('one'), new Candidate('two')];
private matchCandidates$(textToMatch: string): Observable<Candidate> {
return from(this.listOfCandidates)
.pipe(
map(f => {
f.regExp = this.getRegExp(f); // sequential
return f;
}),
map(cand: Candidate => {
this.getPatterns$().subscribe(patterns => {
if (....do some regexp matching...){
cand.matchingRegularExpression = pattern;
}
});
})
)
I tried using mergeMap or switchMap, but those seem to be used when you want to merge 1 outer object with n inner ones. But my inner observable should simply extend my Candidate object and emit 2 values in this example.
So first, it's considered as a bad practice because you completely lose the reference of your second subscription, which could result in a leak if the source continue emitting.
Fortunately we have an operator called switchMap, it allows us to switch on a new observable, and thus, avoid subscribing inside the observer function.
The main difference between switchMap and other flattening operators is the cancelling effect. On each emission the previous inner observable (the result of the function you supplied) is cancelled and the new observable is subscribed. You can remember this by the phrase switch to a new observable.
edit: add snippet
class Candidate {
public name: string;
public matchingRegularExpression: RegExp;
}
listOfCandidates = [new Candidate('one'), new Candidate('two')];
private matchCandidates$(textToMatch: string): Observable<Candidate> {
return from(this.listOfCandidates)
.pipe(
map(f => {
f.regExp = this.getRegExp(f); // sequential
return f;
}),
switchMap(cand: Candidate => {
return this.getPatterns$().pipe(
map(patterns => {
if (....do some regexp matching...){
cand.matchingRegularExpression = pattern;
}
return cand
})
)})
)

RXJS Observable - How to call next from outside of the Observable's constructor

I am building a service which exposes an Observable. In this service I receive external function calls which should trigger a next call on the Observable so that various consumers get the subscribe event. During Observer constructor I can call next and everything works great, but how can I access this outside of the constructor so that external triggers can fire next calls?
private myObservable$: Observable<any>;
During service init I do
this.myObservable$ = new Observable(observer => {
observer.next("initial message");
}
Then in other methods of the same service I want to be able to execute something like
this.myObservable$.observer.next("next message");
The above obviously doesn't work, but how can I accomplish this goal?
I'm assuming I'm missing something basic since there must be a way to emit further messages outside of the Observable's initial constructor
You should create a Subject for that
this.myObservable$ = new Subject();
And then you can call at any point:
this.myObservable$.next(...);
Or use subscribe:
this.myObservable$.subscribe(...)
Actually Subject is used for both publisher and subscriber, and here I think you need only to publish your value, so simply use Observable.
By using observable, assign Subscriber to class level variable and then use it, like below code
subscriber: Subscriber<boolean>;
public observe(): Observable<boolean> {
return new Observable<boolean>(subs => {
this.subscriber = subs;
});
}
public callNext() {
if (this.subscriber) {
this.subscriber.next();
this.subscriber.complete();
}
}
Two ways:
Make myObservable$ public:
public myObservable$: Observable;
Encapsulate the observable in a subject stream, and provide a helper to call next:
export class TestService {
public myObservable$: Observable;
private _myObservableSubject: Subject;
constructor() {
this._myObservableSubject = new Subject();
this.myObservable$ = this._myObservableSubject.asObservable();
}
public NextMessage(message?: string): void {
this._myObservableSubject.next(message);
}
}
Observable: You have to call the next() function from inside the constructor and only one time you can subscribe
message = new Observable((observer)=>{
observer.next(9);
})
this.messsage.subscribe((res)=>{
console.log(res)
})
output: 9
Subject: You have to call next() function from outside the constructor and multiple times you can subscribe.
The subject does not store any initial value before subscribe.
messsage = new Subject()
this.messsage.next(3)
this.messsage.subscribe((res)=>{
console.log(' A '+res)
})
this.messsage.next(4)
this.messsage.next(5)
this.messsage.subscribe((res)=>{
console.log(' B '+res)
})
this.messsage.next(6)
output:
A 4
A 5
A 6
B 6
BehaviorSubject: You have to call next() function from outside the constructor and multiple times you can subscribe.
The BehaviorSubject does store only one initial value before subscribe.
messsage = new BehaviorSubject ()
this.messsage.next(3)
this.messsage.subscribe((res)=>{
console.log(' A '+res)
})
this.messsage.next(4)
this.messsage.next(5)
this.messsage.subscribe((res)=>{
console.log(' B '+res)
})
this.messsage.next(6)
output:
A 3
A 4
A 5
B 5
A 6
B 6
I ended up combining a couple of things:
olsn's answer, which nicely demonstrates Subject's ease of use
Ravi's answer, which correctly points out that we only want an Observable exposed
a more functional approach, because this and Class give me the shivers
TypeScript typings for generic use
const createObservableWithNext = <T>(): {
observable: Observable<T>;
next: (value: T) => void;
} => {
const subject = new Subject<T>();
const observable = subject.asObservable();
const next = (value: T) => subject.next(value);
return {
observable,
next,
};
};

Resources