How to use switchMap in RxJs Subsciption array? - javascript

I want to use switchMap in my subscriptions array, I want to call invokeRequest method which triggers http requests, basically I want to cancel subscription if same http call is triggered, can anyone please help.
private subscriptions: Subscription[] = [];
this.subscriptions.push(trigger.pipe(skip(1)).subscribe((e) =>
this.invokeRequest(callConfig, e))
);

You can use switchMap in the pipe like below:
private subscriptions: Subscription[] = [];
this.subscriptions.push(
trigger.pipe(
skip(1),
switchMap((e) => this.invokeRequest(callConfig, e))
).subscribe(resp => {
// do something with the `invokeRequest` response
})
);
This brings two main benefits:
As you pointed out, when trigger emits a new value, the previous invokeRequest is cancelled (if it's still pending) and a new one is started.
When you unsubscribe() your subscriptions (e.g when the component is destroyed), if there is some request pending, it's cancelled too. The way it was before (being called within the trigger.subscribe() callback) it would not cancel the request.

Related

How to maintain order of subscribers with async operations

I have an angular component which subscribes to its route params. Whenever the route params change, it reads the id from the params and calls a function to look up the record for that id. The function returns a promise. Upon resolving, the component sets a property on itself for the purposes of databinding.
In some cases, it seems that the responses are getting out of order, and the wrong value is set. Here's what it looks like:
ngOnInit() {
this.route.params.subscribe((params) => {
const id = params['id'];
console.log('processing', id);
this.service.getPerson(id).then((person) => {
console.log('processed id', id);
this.person = person; // sometimes this happens out of order
}
}
}
So, let's say I have a textbox where I filter by name, and as I type, navigation events occur where id gets the first result for that name. Sometimes I see this:
processing 1
processing 2
processed 2
processed 1 <---- PROBLEM
What is the most elegant way to solve this? I have considered converting the promise to an observable and trying to cancel it, but that seems heavy-handed. I am trying to figure out if there's a way to serialize subscriptions, so that the second 'processing' doens't begin until the first one completes.
You'll want to make use of either concatMap or switchMap. concatMap will make sure the previous request is finished processing before doing the next one. switchMap will also ensure the order, except it will abort the processing of the prior request if it is still processing when the next request comes in.
ngOnInit() {
this.route.params.pipe(
map(params => params['id']),
tap(id => console.log('processing', id)),
concatMap(id => fromPromise(this.service.getPerson(id)).pipe(
tap(person => console.log('processed id', id))
)),
tap(person => this.person = person)
)
.subscribe();
}
You could add a 'request id' to each call, that gets returned back to you. Then in the callback you can check if the req id is higher than the last one you processed.
It's not ideal for little calls with single-integer responses, but I hid mine in the headers and used an interceptor/filter to echo that header back to the client on every request.

How to empty an observable in angular 4

I have created an observable, which is given below
private permissionSubject = new Subject<any>();
permissionObservable$ = this.permissionSubject.asObservable();
constructor( public apiService: ApiService) { }
updatePermissionsData(permissionData){
this.permissionSubject.next(permissionData);
}
getPermissions(){
return this.apiService.get('/getUserPrivileges')
.map(data => data)
}
Here what I am doing is, whenever I am getting the data, I am pushing the data to Observable
Ex: consider an observable -> [1, 2] and pushing 3 as it is new, data
now observable will become [1, 2, 3]
But I want to remove 1, 2 value from Observable before pushing 3 to it. How can I do that?
Is Observable.empty() will do that, if it can, how can I update my code?
I have seen many questions in stackoverflow, but nothing helped :-( that's why I am asking this question again...
Updated code
Subscribing observable
checkPermissions() {
this.checkPermService.permissionObservable$.subscribe(
// Below one is getting executed for so many times whenever
observable get new data (stream data)
data => {
this.saveMenuItemsOnPermissions(data)
}
)
}
I think there is a misunderstanding of how Observables work. You have no buffer/memory structure in your code.
Your Code explained
// instance of a subject.
// Subjects don't have memory!! The stream is pushed to subscribers once.
private permissionSubject = new Subject<any>();
// Here you make a restriction on `permissionObservable$`, so it listens, but doesn't publish
permissionObservable$ = this.permissionSubject.asObservable();
// constructor instanciates apiService
constructor( public apiService: ApiService) { }
// each time this function is called, permissionData is pushed through
// permissionObservable and permissionObservable$ subscribers.
updatePermissionsData(permissionData){
this.permissionSubject.next(permissionData);
}
// calls a service and waits for subscription (http call I suppose)
// the map function is useless BTW
getPermissions(){
return this.apiService.get('/getUserPrivileges')
.map(data => data)
}
Observable.empty()
create an Observable that emits no items but terminates normally
Observable.empty() is not a method !! It is an observable whose purpose is to :
emit nothing
hang the stream
Edit:
If you just want to ignore the 2 first elements of an observable, you can use skip operator.
Skip operator:
Skip allows you to ignore the first x emissions from the source.
Generally skip is used when you have an observable that always emits
certain values on subscription that you wish to ignore. Perhaps those
first few aren't needed or you are subscribing to a Replay or
BehaviorSubject and do not need to act on the initial values. Reach
for skip if you are only concerned about later emissions.
// Below one is getting executed for so many times whenever observable get new data (stream data)
checkPermissions() {
this.checkPermService.permissionObservable$.skip(2)
.subscribe( data => {
this.saveMenuItemsOnPermissions(data)
})
}
There are 2 important points to bear in mind:
Subscription must occur before observable starts emitting
checkPermissions will ignore the 2 first received elements during subscription, but it will take all the following others.

Why observable source does not emit values when used in race (or merge) but emits when I manually subscribe to it

I have three observable sources in my code that emit values of the same type.
const setTitle$ = params$.do(
params => this.titleService.setTitle( `${params[1].appname} - ${this.pagename}` )
).switchMap(
() => Observable.of(true)
);
const openDocument$ = params$.switchMap(
params => this.openDocument(params[0].id)
);
const saveDocument$ = params$.switchMap(
params => this.saveDocument(params[0].id)
);
When i use them in race like this
setTitle$.race(
openDocument$,
saveDocument$
).subscribe();
works only setTitle and when i subscribe manually to another two sorces like
const openDocument$ = params$.switchMap(
params => this.openDocument(params[0].id)
).subscribe();
const saveDocument$ = params$.switchMap(
params => this.saveDocument(params[0].id)
).subscribe();
then they work too. Help me understand why it's going on and how to force to work all sources in race, merge, etc.
From the documentation, the .race() operator does this:
The observable to emit first is used.
That is why, you will only get ONE emission, because only one out of the three observables that emits first will get emitted.
What you are looking for is .forkJoin() or .combineLatest().
If you want all the observables to execute in parallel and wait for ALL of them to come back as one observables, use .forkJoin():
Observable
.forkJoin([...setTitle$, openDocument$, saveDocument$])
.subscribe(([setTitle, openDocument, saveDocument]) => {
//do something with your your results.
//all three observables must be completed. If any of it was not completed, the other 2 observables will wait for it
})
If you however wants to listen to every emission of all the observables regardless when they are emitted, use .combineLatest():
Observable
.combineLatest(setTitle$, openDocument$, saveDocument$)
.subscribe(([setTitle, openDocument, saveDocument]) => {
//do something with your your results.
// as long as any of the observables completed, it will be emitted here.
});
Problem was with shared params source.
const params$ = this.route.params.map(
routeParams => {
return {
id: <string>routeParams['id']
};
}
).combineLatest(
this.config.getConfig()
).share();
I have shared it with share operator. But in this article from the first comment to my question i found this:
When using multiple async pipes on streams with default values, the .share() operator might cause problems:
The share() will publish the first value of the stream on the first subscription. The first async pipe will trigger that subscription and get that initial value. The second async pipe however will subscribe after that value has already been emitted and therefore miss that value.
The solution for this problem is the .shareReplay(1) operator, which will keep track of the previous value of the stream. That way all the async pipes will get the last value.
I replaced share() with shareReplay(1) and all sources began emitting values.
const params$ = this.route.params.map(
routeParams => {
return {
id: <string>routeParams['id']
};
}
).combineLatest(
this.config.getConfig()
).shareReplay(1);
Thanks to everyone for help!

Better way to use fork join subsequent to another observable

I have a login process that has fairly complicated login variations and has to be scalable to easily add more in the future. So initially the user is authenticated in the typical manner and a user object is returned. Then I must make additional http calls to get information that will determine the various requirements before the user is granted access to the app. This is done using some of the values returned in the user object. I want to write the code in a way that I can easily add http calls without changing current code so I thought using fork join for the subsequent calls would be good since they can be done in parallel. Below is my working code.
I can easily add new requests to the fork join call and while it doesn't look too bad to me I have been told nested subscriptions is a code smell and typically bad practice. Any ideas on how to do this better would be great.
Thanks.
this.authenticate.login(this.model)
.subscribe(
_data => {
this.subscription = Observable.forkJoin(
this.devicesHttp.getDevicesByMacAddress(this.macAddress),
this.teamsService.getTeamsByUserId(_data['userId'])
);
this.subscription.subscribe(
_data => {
// Check login type and other stuff...
}
);
}
);
For example like this using the concatMap() operator:
this.authenticate.login(this.model)
.concatMap(_data => Observable.forkJoin(
this.devicesHttp.getDevicesByMacAddress(this.macAddress),
this.teamsService.getTeamsByUserId(_data['userId'])
))
.subscribe(_data => {
// Check login type and other stuff...
});
The Observables in forkJoin will run in parallel and forkJoin will wait until they both finish.
Also concatMap() waits until the inner Observable completes and then pushes the result further.
In 2021 this must be written with pipe, stand-alone operators, array in forkJoin and Observer argument in subscribe:
import { concatMap, forkJoin } from 'rxjs';
this.getFirst().pipe(
concatMap(data =>
forkJoin([
this.getSecond(data),
this.getThird(data)
])
)
).subscribe({
next: result => ...,
error: e => ...
});
How about this:
this.authenticate.login(this.model)
.switchMap(data => Observable.forkJoin(
this.devicesHttp.getDevicesByMacAddress(this.macAddress),
this.teamsService.getTeamsByUserId(data['userId'])
))
.subscribe(...,...)

.asObservable dont want to work with Observable.forkJoin

I have service:
export class ConfigService {
private _config: BehaviorSubject<object> = new BehaviorSubject(null);
public config: Observable<object> = this._config.asObservable();
constructor(private api: APIService) {
this.loadConfigs();
}
loadConfigs() {
this.api.get('/configs').subscribe( res => this._config.next(res) );
}
}
Trying to call this from component:
...
Observable.forkJoin([someService.config])
.subscribe( res => console.log(res) ) //not working
someService.config.subscribe( res => console.log(res) ) // working
...
How can i use Observable.forkJoin with Observable variable config?
I need to store configs in service and wait unlit them and others request not finished to stop loader.
Since you're using BehaviorSubject you should know that you can call next() and complete() manually.
The forkJoin() operator emits only when all of its source Observables have emitted at least one values and they all completed. Since you're using a Subject and the asObservable method the source Observable never completes and thus the forkJoin operator never emits anything.
Btw, it doesn't make much sense to use forkJoin with just one source Observable. Also maybe have a look at zip() or combineLatest() operators that are similar and maybe it's what you need.
Two very similar question:
Observable forkJoin not firing
ForkJoin 2 BehaviorSubjects

Categories