Kind of beginner question here: in an Ionic2 component, I have 2 different service calls using Observables:
getTimelineEvents() {
this.receiptsEventsRequestState = RequestState.Pending;
this.chargesEventsRequestState = RequestState.Pending;
this.miscService.getCustomerReceiptsEvents()
.subscribe(
(events: TimelineEvent[]) => {
this.receiptsEventsRequestState = RequestState.Success;
this.receiptsEvents = events;
},
);
this.miscService.getCustomerChargesEvents()
.subscribe(
(events: TimelineEvent[]) => {
this.chargesEventsRequestState = RequestState.Success;}
this.chargesEvents = events;
);
}
I'd like to know when both getCustomerReceiptsEvents and getCustomerChargesEvents are successful sothat I can call another method (this method needs chargesEvents and receiptsEvents data).
Thanks.
You can wait for both observables to complete and get the values they emitted by using the forkJoin operator. They will still be executed in parallel.
Observable.forkJoin(
this.miscService.getCustomerReceiptsEvents(),
this.miscService.getCustomerChargesEvents(),
)
.subscribe(([receipts, charges] => {
console.log('Results', receipts, charges)
})
Related
return this.usersTableService.fetchRequestedPageUsersIds(request).pipe(
switchMap((idsToFetch) => {
requestedIds = idsToFetch;
return [this.usersTableService.getNewIdsToFetch(requestedIds, entities), of(idsToFetch)];
}),
//.......?(([newIds, idsToFetch]) => {
return this._fetchNewUsersFromAPI(requestedIds, request, newIds, entities);
}),
catchError((err) => of(loadPageFail(err)))
);
what operator should I use in order to get the value of the return tuple before ?
You can use forkJoin for this
return this.usersTableService.fetchRequestedPageUsersIds(request).pipe(
switchMap((idsToFetch) => {
return forkJoin([this.usersTableService.getNewIdsToFetch(requestedIds, entities), of(idsToFetch)]);
}),
mergeMap(([newIds, idsToFetch]) => {
return this._fetchNewUsersFromAPI(requestedIds, request, newIds, entities);
}),
catchError((err) => of(loadPageFail(err)))
)
You would normally use the map operator(https://stackblitz.com/edit/so-tuple-map?file=index.ts):
const obs$ = of(1).pipe(map(y => ['abc', 'def']), map(([str1, str2]) => str1 + str2))
But if you try that you will encounter other issues with your code ie:
its not good practice to store a local variable inside a switchMap then return it using of
_fetchNewUsersFromAPI needs to be inside a switchMap
Ultimately you'll still be faced with the fundamental problem of how to pass parameters down the observable chain, which I suspect is how you've ended up in this situation to begin with.
There is currently a bountied question asking about the same problem here: How to pass results between chained observables
IMO the best solution from that question is to use nested pipes ie:
const newUsers$ = requestsSubject.pipe(
switchMap(request =>
this.usersTableService.fetchRequestedPageUsersIds(request).pipe(
switchMap(idsToFetch =>
this.usersTableService.getNewIdsToFetch(idsToFetch).pipe(
switchMap(newIds =>
this._fetchNewUsersFromAPI(idsToFetch, request, newIds, entities)
)
)
)
)
)
);
An alternative way using await and toPromise:
function getUsers(request){
const idsToFetch = await this.usersTableService.fetchRequestedPageUsersIds(request).toPromise();
const newIds = await this.usersTableService.getNewIdsToFetch(idsToFetch, entities).toPromise();
const newUsers = await this._fetchNewUsersFromAPI(idsToFetch, request, newIds, entities).toPromise();
return newUsers;
}
I have the following searchService.search method that returns a forkJoin of two api calls.
I want the calls to execute simultaneously which they are but I also want each response back as a single object that can be passed into my SearchSuccess action and processed immediately without waiting for all calls to complete. Currently they are returning as an array of responses and only upon completion of both API calls - as this is what forkJoin is used for.
My issue is that I'm struggling to find another operator that does what I want.
Or perhaps the code pattern requires some redesign?
action:
#Effect()
trySearch: Observable<Action> = this.actions$.pipe(
ofType(SearchActionTypes.TrySearch),
switchMap((action: TrySearch) =>
this.searchService.search(action.payload)
.pipe(
map((data) => new SearchSuccess(data)),
catchError(error => of(new SearchFail(error))),
),
),
);
SearchService (snippet):
search(searchForm: SearchForm): Observable<any> {
const returnArray = [];
if (searchForm.searchClients) {
const searchClientParams = new Search();
searchClientParams.searchPhrase = searchForm.searchPhrase;
searchClientParams.type = SearchType.Client;
const searchClients = this.objectSearch(searchClientParams);
returnArray.push(searchClients);
}
if (searchForm.searchContacts) {
const searchContactParams = new Search();
searchContactParams.searchPhrase = searchForm.searchPhrase;
searchContactParams.type = SearchType.Contact;
const searchContacts = this.objectSearch(searchContactParams);
returnArray.push(searchContacts);
}
return Observable.forkJoin(returnArray);
}
If I understand it correctly returnArray contains two Observables and you want to wait until they both complete but still you want to emit each result separately.
Since forkJoin emits all results in a array you could just unwrap it with mergeMap (or concatMap):
this.searchService.search(action.payload)
.pipe(
mergeMap(results => results),
map((data) => new SearchSuccess(data)),
catchError(error => of(new SearchFail(error))),
),
I'm new to rxjs and using redux-observable. The short of it is that I need to make a couple promise requests when i get a connection then output the results. I'm wondering if there is a way to join this into a single map at the end and not have to call store.dispatch multiple times and have the retry work for each individual read. Thanks ahead of time for your comments.
export const handleBleConnectionSuccess = (action$,store,{bleCommunicator}) =>
action$.ofType(c.BLE_CONNECTION_SUCCESS)
.do((a)=>{
Observable.fromPromise(bleCommunicator.readCharacteristic(a.device.id,gattInfo.uuid,gattInfo.firmwareRevision.uuid))
.do((value)=>store.dispatch({type:c.DEVICE_FIRMWARE_VERSION,device:{...a.device,firmwareVersion:value}}))
.retry(3);
Observable.fromPromise(bleCommunicator.readCharacteristic(a.device.id,gattInfo.uuid,gattInfo.modelNumber.uuid))
.do(value=>store.dispatch({type:c.DEVICE_MODEL_NUMBER,device:{...a.device,modelNumber:value}}))
.retry(3);
})
.mapTo({type:'DEVICE_INFORMATION_REQUESTED'});
I'm wondering if there is a way to join this into a single map at the end and not have to call store.dispatch multiple times and have the retry work for each individual read
Yes, there is a better way, and it's possible to do what you want.
From the syntax, I'm guessing that you use ngrx (effects) (and not redux-observable).
So with ngrx/effects you could do it like that:
export const handleBleConnectionSuccess = (
action$,
store,
{ bleCommunicator }
) =>
action$.ofType(c.BLE_CONNECTION_SUCCESS).switchMap(a => {
const readCharacteristic = deviceOrFirmwareUuid =>
bleCommunicator.readCharacteristic(a.device.id, gattInfo.uuid, deviceOrFirmwareUuid);
return Observable.merge(
readCharacteristic(gattInfo.firmwareRevision.uuid)
.map(value => ({
type: c.DEVICE_FIRMWARE_VERSION,
device: { ...a.device, firmwareVersion: value },
}))
.retry(3),
readCharacteristic(gattInfo.modelNumber.uuid)
.map(value => ({
type: c.DEVICE_MODEL_NUMBER,
device: { ...a.device, modelNumber: value },
}))
.retry(3),
{ type: 'DEVICE_INFORMATION_REQUESTED' }
);
});
My scenario is that I add a record set to a host zone via aws sdk. When adding a record set, the aws sdk has a GetChange call that can be used to get that status. Here is the code I am currently doing:
this._adminService.registerDomain(caseWebsiteUrl.Url).
subscribe(id => {
return Observable.interval(5000).flatMap(() => {
return this._adminService.getChange(id);
}).
takeWhile((s) => s.ChangeInfo.Status.Value !== 'INSYNC').subscribe(
() => {
},
() => {
},
() => this.urlStatus = 'fa fa-check');
});
In the above code, I want to call registerDomain and after that has been successful, I want to call getChange every 5 seconds until the Status.Value !== 'INSYNC'
A few questions:
What is flatMap doing?
Is it possible to do this without 2 subscribe calls?
If I don't need the next or error callbacks, but I need the complete, is it necessary to declare empty bodies?
Flatmap aka MergeMap will flatten higher order observables. Thus Observable<Observable<T>> => Observable<T>.
The subscribe inside subscribe is a code smell and can and should be refactored. If you do not need the error/complete handlers you do not need to pass those. For instance:
function registerDomain(caseWebsiteUrl) {
return this._adminService.registerDomain(caseWebsiteUrl.Url)
.concatMap(registerId => Observable.interval(5000)
.mergeMap(() => this._adminService.getChange(registerId))
.takeWhile((info) => info.ChangeInfo.Status.Value !== 'INSYNC')
)
}
registerDomain.subscribe(res => console.log('res:'+res));
This works based on the assumption and limitations that:
registerDomain() returns an Observable which completes
getChange() will eventually return 'INSYNC'
No error handling has been added (for instance a timeout after 30 seconds? Retry if registerDomain() fails?)
I'm using Angular2 and I have a question about what is the best way to do if I have many observables.
Can I put subscriptions inside each other or put each one in a different method and put the results in class properties?
Example :
ngOnInit() {
this.route.params**.subscribe**(params => {
if (params['id']) {
this.load = true;
this.batchService.getPagesOfCurrentObject(params['id'], "10", "0")
**.subscribe**(result => {
this.stream = result;
if (this.stream.length > 0) {
this.stream.forEach(page => { this.batchService.getPageStreamById
(page.pageId)**.subscribe**(pageStream => {
let base64 = btoa(new Uint8Array(pageStream.data)
.reduce((data, byte)
=> data + String.fromCharCode(byte), ''));
this.pages.push(base64 );
})
return;
});
}
},
error => this.errorService.setError(<any>error),
() => this.load = false
);
}
});
try {
this.customer = this.sharedService.processSelect.subscription.customer;
} catch (err) {
return;
}
}
Having multiple observables is totally fine, this is what reactive programming is about :)
But here your problem is having too much subscribe. Keep in mind that subscribe is a way to create side effect. To have an easy to read code, you should try to use the least possible subscribe.
Your use case is the perfect use case for the mergeMap operator, that allows you to flatten nested observables.
Here what your code would look like
const response$ = this.route.params
.mergeMap(params => {
return this.batchService.getPagesOfCurrentObject(params['id'])
})
.mergeMap(stream => {
return Rx.Observable.merge(stream.map(page => this.batchService.getPageStreamById(page.pageId))
})
.map(pageStream => /* do your stuff with pageStream, base64 ... */)
response$.subscribe(pageStreamData => pages.push(pageStreamData))
See how there is a single subscription that triggers the side-effect that will modify your app's state
Note that I voluntarily simplified the code (removed error handling and checks) for you to get the idea of how to do that.
I hope it will help you thinking in reactive programming :)