undefined when combine combineLatest and switchMap - rxjs

I'm trying to combine two Observables to have few values for the call to service method that accepts two values. But here I have few errors
Argument of type '([filter, sort]: [string, string]) => void' is not assignable to parameter of type '(value: [string, string], index: number) => ObservableInput'.
Type 'void' is not assignable to type 'ObservableInput'.
in console -
You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
at subscribeTo (subscribeTo.js:27)
at innerSubscribe (innerSubscribe.js:71)
at SwitchMapSubscriber._innerSub (switchMap.js:44)
at SwitchMapSubscriber._next (switchMap.js:34)
at SwitchMapSubscriber.next (Subscriber.js:49)
at CombineLatestSubscriber.notifyNext (combineLatest.js:73)
at InnerSubscriber._next (InnerSubscriber.js:11)
at InnerSubscriber.next (Subscriber.js:49)
at BehaviorSubject._subscribe (BehaviorSubject.js:14)
at BehaviorSubject._trySubscribe (Observable.js:42)
Defining Observables and Subjects.
private openSortQuerySubject: BehaviorSubject<string> = new BehaviorSubject<string>(this.formatSortQuery(this.multiSortMeta));
public openSortQuery: Observable<string> = this.openSortQuerySubject.asObservable();
private closedSortQuerySubject: BehaviorSubject<string> = new BehaviorSubject<string>(this.formatSortQuery(this.multiSortMeta));
public closedSortQuery = this.closedSortQuerySubject.asObservable();
Combine observables.
const openQueries$ = combineLatest([this.openFilterQuery, this.openSortQuery]);
const closedQueries$ = combineLatest([this.closedFilterQuery, this.closedSortQuery]);
Use merged values in service method.
openQueries$
.pipe(
switchMap(([filter, sort]) => {
this.alertService
.listAlerts(filter, sort);
}),
tap(v => console.log(v))
)
.subscribe((openAlerts) => {
this.openAlertsCount = openAlerts.length;
this.openAlerts = this.parseAlerts(openAlerts);
});
closedQueries$
.pipe(
switchMap(([filter, sort]) => {
this.alertService
.listAlerts(filter, sort);
})
)
.subscribe((closedAlerts) => {
this.closedAlertsCount = closedAlerts.length;
this.closedAlerts = this.parseAlerts(closedAlerts);
});

You have to return an observable (or something RxJS knows how to turn into an observable) within a switchMap callback.
Not this:
switchMap(([filter, sort]) => {
this.alertService.listAlerts(filter, sort);
})
But this (assuming listAlerts returns an observable, a promise or similar):
switchMap(([filter, sort]) => {
return this.alertService.listAlerts(filter, sort);
})

Related

How to map an Observable with values from another observable

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

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.

What Is the behavior of forkJoin when it is not In a SwitchMap/MergeMap in Epics

I want to understand what happens when I directly pipe to an action$ and try to use forkJoin operator
const action1 = { type: "ACTION_1" };
const action2 = { type: "ACTION_2" };
in a switchMap forkJoin works fine.
export const testForkJoinSwitchMap: Epic<Action> = action$ =>
action$.pipe(
ofType(action1),
switchMap(() =>
forkJoin(
from(fetch("https://api.github.com/users")).pipe(
map((res: any) => {
return res;
})
)
)
),
map((data: any) => {
// do something with data
return action2;
})
);
If I took it out of the switchMap then:
export const testForkJoin: Epic<Action> = action$ =>
action$.pipe(
ofType(action1),
forkJoin(from(fetch("https://api.github.com/users"))).pipe(
map((response: any) => {
return action2;
})
)
);
I get the typing error:
Argument of type 'Observable<{ type: string; }>' is not assignable to parameter of type 'OperatorFunction<{}, Action<any>>'.
I want to know why it does not compile? and the reason of the type mismatch, what makes the epics without the forkJoin invalid in this case?
edit: I know that forkJoin is not meaningful for a single observable, but I put 1 to keep the example smaller
observable.pipe() only takes operators inside it.
forkJoin is an operator which returns an observable that's why you get that error. Argument of type 'Observable' is not assignable to parameter of type 'OperatorFunction' i.e forkJoin which returns an observable is not assignable to type of operator function.
switchMap, on the other hand, is an operator which returns an OperatorFunction and operates on an observable that's why your first approach worked. switchMap(() => someObservable) and someObservable in this case is forkJoin()
It's also evident from your imports. You might have imported forkJoin from rxjs library whereas switchMap from rxjs/operators library.

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

Ignore switchMap return value

I want to resolve an observable but I don't want the return value to replace the previous value in the pipe. Is there any asynchronous tap()? I need an operator like a switchMap but I want to ignore the return.
of(1).pipe(switchMap(() => of(2))).subscribe(console.log); // expected: 1
I could create a custom operator but sure there's something built-in in rxjs.
I ended up with this custom operator. It is like tap but resolves observables (and should be updated to also support promises).
export function switchTap<T, R>(next: (x: T) => Observable<R>): MonoTypeOperatorFunction<T>;
export function switchTap<R>(observable: Observable<R>): MonoTypeOperatorFunction<R>;
export function switchTap<T, R>(
arg: Observable<T> | ((x: T) => Observable<R>)
): MonoTypeOperatorFunction<T> {
const next: (x: any) => Observable<T | R> =
typeof arg === 'function' ? arg : (x: any): Observable<T> => arg;
return switchMap<T, T>(value => next(value).pipe(ignoreElements(), concat(of(value))));
}
Usage:
of(1).pipe(switchTap(of(2))).subscribe(console.log) // 1
or with a function:
of(1)
.pipe(
switchTap(value => {
console.log(value); // value: 1
return of(value + 1);
})
)
.subscribe(console.log); // 1
If you just want to simply ignore the values of the subscribe, then just don't pass in any arguments in the subscribe callback:
of(1).pipe(switchMap(() => of(2))).subscribe(()=>{
console.log('no arguments')
});
If you however want to retain the values of the first observable, things can get tricky. One way is to use Subject to retain the value:
//create a BehaviorSubject
var cache = new BehaviorSubject<any>(0);
of(1).pipe(switchMap((first) => {
cache.next(first);
return of(2);
})).subscribe(() => {
console.log(cache.value) //gives 1
});
Or you can use .map() to alter the values. This is kind of hacky and the code is harder to maintain:
of(1).pipe(switchMap((first) => {
return of(2).map(() => first);
})).subscribe((second) => {
console.log(second) //gives 1 because the values was mapped
});
I do it like so
of(2).pipe(
switchMap( num => this.doSmtg(num), num => num)
).subscribe(num => console.log(num)); // 2
Second param of switchmap receives two value the one passed to this.doSmtg and the value returned by doSmtg(num)'s observable.
For anyone new having the same problem I would advise using the resultSelector parameter supported by switchMap and other RxJS mapping operators.
Example:
switchMap(1 => of(2), (one, two) => one)
For further reading: https://www.learnrxjs.io/operators/transformation/mergemap.html
I think you could use delayWhen operator to achieve a similar functionality.
of(1)
.pipe(
delayWhen(value => {
console.log(value); // value: 1
return of(value + 1);
})
).subscribe(console.log); // 1

Resources