How to map an Observable with values from another observable - rxjs

I obtain my Ofertas here
getOfertasByYear(year:number): Observable<Oferta[]> {
return this.http.get<Oferta[]>(`${this.urlWebAPI}/ofertas/year/${year}`)
.pipe(
map(ofertas=>
ofertas.map(oferta=>({
...oferta,
añoPresentada:new Date(oferta.fechaPresentacionFulcrum).getFullYear(),
organismoId:¿¿¿???
}) as Oferta)
),
tap(data => console.log('OfertasService-getOfertasByYear(): ', data)
),
catchError(this.handleError)
)
}
But I need to calculate his organismoId and that is here
getOrganismoDeOferta(ofertaId:string): Observable<Organismo> {
return this.http.get<Organismo>(`${this.urlWebAPI}/organismos/oferta/${ofertaId}`)
.pipe(
tap(//data=>console.log('OfertasService-getOrganismos(): ', data)
),
catchError(this.handleError)
)
}
And I don't know how to pass the result of this Observable to te mapped property
getOfertasByYear(year:number): Observable<Oferta[]> {
return this.http.get<Oferta[]>(`${this.urlWebAPI}/ofertas/year/${year}`)
.pipe(
map(ofertas=>
ofertas.map(oferta=>({
...oferta,
añoPresentada:new Date(oferta.fechaPresentacionFulcrum).getFullYear(),
organismoId:this.getOrganismoDeOferta(oferta.id).subscribe(data=>{
¿¿¿¿??????
})
}) as Oferta)
),
tap(data => console.log('OfertasService-getOfertasByYear(): ', data)
),
catchError(this.handleError)
)
}
I subscribe to it but I don't know how to make the assignment
I have tried to obtain all Ofertas and All Concursos but neither
ofertas$ = this.dataService.getOfertas();
concursos$ = this.dataService.getConcursos();
ofertasConOrganismos$ = forkJoin([
this.ofertas$,
this.concursos$
])
.pipe(
map(([ofertas, concursos]) =>
ofertas.map(oferta => ({
...oferta,
organismoId: concursos.find(c => c.id == oferta.concursoId).organismoId
}) as Oferta))
);
But I get this error:
Cannot read properties of undefined (reading 'organismoId')
Any idea, please?
Thanks

Instead of using a plain map and then calling .subscribe(), you can use a "Higher-Order Mapping Operator" to handle the inner subscription for you. In this case, let's use switchMap.
The idea is to return an observable inside switchMap that emits the data you need. Since you need to make multiple calls, we can leverage some help from forkJoin.
With forkJoin you pass in an array of observables, and it will emit an array of the results. So here below we map the array of Oferta to an array of observables that will each emit the Oferta with the organismoId appended:
getOfertasByYear(year: number): Observable<Oferta[]> {
return this.http.get<Oferta[]>(`${this.urlWebAPI}/ofertas/year/${year}`).pipe(
switchMap(ofertas => forkJoin(
ofertas.map(oferta => this.appendOrganismo(oferta))
)),
catchError(this.handleError)
)
}
Nothing too fancy for the definition of appendOrganismo(); we just make the http call, then map the result to the desired shape:
private appendOrganismo(oferta: Oferta) {
return this.getOrganismoDeOferta(oferta.id).pipe(
map(organismo => ({
...oferta,
añoPresentada: new Date(oferta.fechaPresentacionFulcrum).getFullYear(),
organismoId: organismo.id
}))
);
}

Related

rxjs ForkJoin but have it complete when 1 of 2 observables give a value

I'm new to rsjx but I'm looking for a solution like the Forkjoin but it should complete when 1 of the 2 observables have a value. It needs to be like a ForkJoin because I need to know which observable got a value.
example:
I'm loading categories and I have an autocomplete. Categories is an observable and the valueChanges is returned as an observable.
#Select(IngredientCategoryState.selectIngredientCategories) ingredientCategories$!: Observable<IngredientCategory[]>;
this.filter = this.ingredientForm.controls['category'].valueChanges.pipe(
map((data) => {
if (typeof data === 'object') {
return data.name;
}
return data;
})
)
Then I used the forkJoin but then it requires both observables to have a value
this.filteredIngredientCategories$ = forkJoin({
ingredientCategories: this.ingredientCategories$,
filter: this.filter
}).pipe(
map(({ ingredientCategories, filter }) => {
return this._filter(ingredientCategories, filter);
})
);
UPDATE
I solved it with a combineLatest and triggering the autocomplete field
combineLatest([this.ingredientCategories$, this.filter$])
.pipe(
map((data) => {
return this._filter(data[0], data[1]);
})
).subscribe((ingredientCategories) => {
this.filteredIngredientCategories = ingredientCategories;
});
this.ingredientForm.controls['category'].setValue('');
You can use race to use the first source to emit.
I need to know which observable got a value
Instead of "knowing" which one emitted, you can transform the output of each source to return the object shape you need:
this.filteredIngredientCategories$ = race(
this.ingredientCategories$.pipe(
map(ingredientCategories => ({ ingredientCategories, filter: undefined }))
),
this.filter.pipe(
map(filter => ({ filter, ingredientCategories: undefined }))
)
).pipe(
map(({ ingredientCategories, filter }) => {
return this._filter(ingredientCategories, filter);
})
);
Here's a working StackBlitz demo.

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

rxjs - combining inner observables after filtering

I call backend that respond with:
[
"https://some-url.com/someData1.json",
"https://some-url.com/someData2.json"
]
Each JSON can have following schema:
{
"isValid": boolean,
"data": string
}
I want to get array with all data, that have isValid is set to true
backend.get(url)
.pipe(
mergeMap((urls: []) =>
urls.map((url: string) =>
backend.get(url)
.pipe(
filter(response => response.isValid),
map(response => response.data)
)
)
),
combineAll()
)
When both .json have "isValid" set to true, I get array with both data.
But when one of them has "isValid" set to false observable never completes.
I could use mergeAll instead of combineAll, but then I receive stream of single data not collection of all data.
Is there any better way to filter out observable?
As you said, the inner observable never emits, because filter does not forward the only value that is ever emitted by the backend.get observable. In that case, the operator subscribing on that observable - in your case combineAll - will also never receive any value and cannot ever emit itself.
What I would do is just move the filtering and mapping to combineAll by providing a project function, like that:
backend.get(url)
.pipe(
mergeMap((urls: string[]) =>
urls.map((url: string) => backend.get(url))
),
combineAll(responses =>
responses
.filter(response => response.isValid)
.map(response => response.data)
)
)
See if that works for you ;)
import { forkJoin, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
interface IRes {
isValid: boolean;
data: string;
}
interface IResValid {
isValid: true;
data: string;
}
function isValid(data: IRes): data is IResValid {
return data.isValid;
}
const res1$: Observable<IRes> = backend.get(url1);
const res2$: Observable<IRes> = backend.get(url2);
// When all observables complete, emit the last emitted value from each.
forkJoin([res1$, res2$])
.pipe(map((results: IRes[]) => results.filter(isValid)))
.subscribe((results: IResValid[]) => console.log(results));

RXJs Service to return multiple observables

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

Resources