RXJs Service to return multiple observables - rxjs

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))),
),

Related

What operator is used to get several values from observable

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;
}

Chaining rxjs 6 observables

I have to create a queue of ajax requests and group the result but i have no clue about to accomplish this.
Let's say i have an array like this:
const actors = [
"Al Pacino",
"Robert De Niro"
];
I have to iterate over it and for each values make an api call:
export const getMovies = action$ =>
action$.pipe(
ofType(LOAD_REQUEST),
// iterate over the array
// make api call with the actor name
// for each result, make a second api call with the id of the actor (get in the response.results.id)
// group the result in an array with all films of all actors of the array passed in the payload
);
Im stuck with switchMap, pipe ... and don't know the correct way to accomplish this.
Edit Tried your solution Valeriy but got this error:
export const getMovies = action$ =>
action$.pipe(
ofType(LOAD_REQUEST),
switchMap(({ payload }) =>
combineLatest(
payload.map(a => {
return ajax
.getJSON(actor(a))
.pipe(map(response => console.log(response)));
})
)
)
);
TypeError: You provided 'function (source) {
return source.lift.call(Object(_observable_from__WEBPACK_IMPORTED_MODULE_2__["from"])([source].concat(observables)), new _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__["CombineLatestOperator"](project));
}' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
If I understand you correctly, you are trying to achieve something like this:
export const getMovies = action$ => action$.pipe(
ofType(LOAD_REQUEST),
switchMap(() => {
// group the result in an array with all films of all actors of the array passed in the payload
return combineLatest(
// iterate over the array
...actors.map(actorName => {
// make api call with the actor name
return loadActor(actorName).pipe(
// for each result, make a second api call with the id of the actor (get in the response.results.id)
switchMap(response => loadActorFilms(response.results.id))
);
})
);
})
);
I've used combineLatest to group multiple observables together.

RxJS throttle for AJAX requests

I want to create a function that will make AJAX requests to backend. And if this function is called many times at the same time, then it should not make many identical requests to the server. It must make only 1 request.
For example:
doAJAX('http://example-1.com/').subscribe(res => console.log); // must send a request
doAJAX('http://example-1.com/').subscribe(res => console.log); // must NOT send a request
doAJAX('http://example-2.com/').subscribe(res => console.log); // must send a request, bacause of different URL
window.setTimeout(() => {
doAJAX('http://example-2.com/').subscribe(res => console.log); // must send a request because too much time has passed since the last request
}, 3000)
All function calls should return a result, as if the request was actually made.
I think for this purpose I can use RxJS library.
I have done this:
const request$ = new Subject < string > ();
const response$ = request.pipe(
groupBy((url: string) => url),
flatMap(group => group.pipe(auditTime(500))), // make a request no more than once every 500 msec
map((url: string) => [
url,
from(fetch(url))
]),
share()
);
const doAJAX = (url: string): Observable <any> {
return new Observable(observe => {
response$
.pipe(
filter(result => result[0] === url),
first(),
flatMap(result => result[1])
)
.subscribe(
(response: any) => {
observe.next(response);
observe.complete();
},
err => {
observe.error(err);
}
);
request$.next(url);
});
}
I create request$ subject and response$ observable. doAjax function subscribes for response$ and send URL string to request$ subject. Also there are groupBy and auditTime operators in request$ stream. And filter operator in doAJAX function.
This code works but I think it is very difficult. Is there a way to make this task easier? Maybe RxJS scheduler or not use RxJS library at all
As the whole point of this is to memoize Http results and delay repeated calls, you might consider your own memoization. Example:
const memoise = (func) => {
let cache: { [key:string]: Observable<any> } = {};
return (...args): Observable<any> => {
const cacheKey = JSON.stringify(args)
cache[cacheKey] = cache[cacheKey] || func(...args).pipe(share());
return cache[cacheKey].pipe(
tap(() => timer(1000).subscribe(() => delete cache[cacheKey]))
);
}
}
Here is a Stackblitz DEMO

RxJS: forkJoin mergeMap

I'm trying to make multiple http requests and get returned data in one object.
const pagesToFetch = [2,3]
const request$ = forkJoin(
from(pagesToFetch)
.pipe(
mergeMap(page => this.mockRemoteData(page)),
)
)
mockRemoteData() return a simple Promise.
After first Observable emits (the once created from first entry of pagesToFetch the request$ is completed, second value in not included. How can I fix this?
You can turn each value in pagesToFetch into an Observable and then wait until all of them complete:
const observables = pagesToFetch.map(page => this.mockRemoteData(page));
forkJoin(observables)
.subscribe(...);
Or in case it's not that simple and you need pagesToFetch to be an Observable to collect urls first you could use for example this:
from(pagesToFetch)
.pipe(
toArray(),
mergeMap(pages => {
const observables = pages.map(page => this.mockRemoteData(page));
return forkJoin(observables);
}),
)
.subscribe(...);
Try the below sample format...
Observable.forkJoin(
URL 1,
URL 2
).subscribe((responses) => {
console.log(responses[0]);
console.log(responses[1]);
},
error => {console.log(error)}
);

Ionic2/angular: how to know when 2 different observables are terminated?

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)
})

Resources