I am using forkJoin to subscribe multiple inner observable. How can I flat nested array to single level array.
const x$ = of([1, 2, 3, 4]);
const y$ = of([2, 4]);
x$.pipe(
switchMap((t) => {
const innerArr$ = t.map((z) => y$.pipe(map((_) => _.map((y) => y * z))));
return forkJoin(innerArr$);
})
).subscribe(console.log);
Playground Link: Rxjs stackblitz
Expected Output:
[2,4,4,8,6,12,8,16]
You can try this
x$.pipe(
switchMap((t) => {
const innerArr$ = t.map((z) => y$.pipe(map((_) => _.map((y) => y * z))));
return forkJoin(innerArr$);
}),
mergeMap((aa) => from(aa).pipe(mergeMap((a) => a)))
).subscribe(console.log);
The key here is
mergeMap is actually a flatten Observable-like objects (actually
mergeMap was formerly known as flatMap)
an Array is treated as an Observable since it actually can represent a stream of data, which is what an Observable is
+1 to the answer by #Picci if you want a stream of numbers.
If you want to wind up with a single array instead, you can flatten the result in the subscription:
x$.pipe(
switchMap((t) => {
const innerArr$ = t.map((z) => y$.pipe(map((_) => _.map((y) => y * z))));
return forkJoin(innerArr$);
})
)
.subscribe((res) => {
console.log([].concat.apply([], res));
})
Related
I have app that opens stream where server pumps SSEs.
I want the loop to run until desired value arrives, then leave the loop and continue.
I've looked at takeWhile operator, but couldn't find a way to implement it. I also don't know how could I unsubscribe and since the stream never completes...
const stream = this.sseService.returnAsObservable();
for await (const data of eachValueFrom(stream)) {
console.log(data);
if (data.jobid === "JOB05879") {
this.sseService.stopConnection();
// how to get out now?
}
}
console.log('we are out');
Generally, mixing promises and observables is an anti-pattern. If you don't mind that, then here's a way to get only the first data where data.jobid === "JOB05879". You don't need for-await as you're just expecting 1 value from this stream.
const stream = this.sseService.returnAsObservable().pipe(
filter(data => data.jobid === "JOB05879"),
first()
);
data = await stream.toPromise();
console.log(data);
this.sseService.stopConnection();
console.log('we are done');
Without promises:
const stream = this.sseService.returnAsObservable().pipe(
filter(data => data.jobid === "JOB05879"),
first()
).subscribe({
next: data => {
console.log(data);
this.sseService.stopConnection();
},
error: _ console.log("Data with jobid JOB05879 not found"),
complete: () => console.log("We are done");
});
Update #1: Using forkJoin
You wrote the following which first grabs an array of finito and then uses that array as input for your notes.
const finito = await forkJoin([
this.jobsService.submitTestJob('blabla1').pipe(first()),
this.jobsService.submitTestJob('blabla2').pipe(first())
]).toPromise();
const notes = await forkJoin([
this.sseService.returnAsObservable().pipe(
filter((d: any) => d.jobid === finito[0].jobid),
first()
),
this.sseService.returnAsObservable().pipe(
filter((d: any) => d.jobid === finito[1].jobid),
first()
)
]).toPromise();
That should work, though there's a bunch of error checking you should do.
This can actually be simplified using RxJS operators so that you're not waiting on each finito
const notes = await merge(
this.jobsService.submitTestJob('blabla1').pipe(first()),
this.jobsService.submitTestJob('blabla2').pipe(first()),
this.jobsService.submitTestJob('blabla3').pipe(first()),
this.jobsService.submitTestJob('blabla4').pipe(first()),
this.jobsService.submitTestJob('blabla5').pipe(first()),
).pipe(
mergeMap(finito => this.sseService.returnAsObservable().pipe(
filter((d: any) => d.jobid === finito.jobid),
first()
)),
toArray()
).toPromise();
And another step to make it even more concise, if you'd like:
const notes = await from([
"blabla1",
"blabla2",
"blabla3",
"blabla4",
"blabla5"
]).pipe(
mergeMap(blabla =>
this.jobsService.submitTestJob(blabla).pipe(first())
),
mergeMap(finito => this.sseService.returnAsObservable().pipe(
filter((d: any) => d.jobid === finito.jobid),
first()
)),
toArray()
).toPromise();
Even more concise:
const notes = await from([1,2,3,4,5]).pipe(
map(num => `blabla${num}`),
mergeMap(blabla =>
this.jobsService.submitTestJob(blabla).pipe(first())
),
mergeMap(finito => this.sseService.returnAsObservable().pipe(
filter((d: any) => d.jobid === finito.jobid),
first()
)),
toArray()
).toPromise();
Making only 1 call to sseService
From the look of your example code, it seems it should be possible for you to make just one call to this.sseService.returnAsObservable(). It can filter through any of the allowable jobs.
That might look like this:
const params = [1,2,3,4,5];
const notes = await from(params).pipe(
map(num => `blabla${num}`),
mergeMap(blabla =>
this.jobsService.submitTestJob(blabla).pipe(first())
),
toArray(),
mergeMap(finitoArray => this.sseService.returnAsObservable().pipe(
filter(d => finitoArray.map(f => f.jobid).includes(d.jobid))
)),
take(params.length),
toArray()
).toPromise();
and to take this example back to the two-stage code you wrote, that would look like this:
const params = [1,2,3,4,5];
const streams = params
.map(num => `blabla${num}`)
.map(blabla => this.jobsService.submitTestJob(blabla).pipe(first())
const finito = await forkJoin(streams).toPromise();
const notes = await this.sseService.returnAsObservable().pipe(
filter(d => finitoArray.map(f => f.jobid).includes(d.jobid)),
take(params.length),
toArray()
).toPromise();
For Example, I have a stream a number of numbers say 1,2,3,4 and so on. I want to sense each of these data and whenever it's even I want to emit true in another data stream. keeping the source data stram[1,2,3,4] as is.
I suggest you to share your source and subscribe to it twice.
...
private source$ = of(1,2,3,4,5,6).pipe(share());
private evenNumberObservable$ = this.source$.pipe(
map(x => x % 2 === 0),
filter(x => !!x)
);
//or
//private evenNumberObservable$ = this.source$.pipe(
// filter(x => x % 2 === 0),
// map(x => true)
//);
public ngOnInit() {
this.evenNumberObservable$.subscribe(x => console.log(x));
this.source$.subscribe(x => console.log(x))
}
...
whole code
Demo: https://stackblitz.com/edit/rxjs-unsubscribe-issue?file=index.ts
Below code is not working
Error: Cannot read property 'unsubscribe' of undefined
const a = (): Observable<any> =>
new Observable(sub => {
sub.next(1);
return () => {
console.log('unsubscribe');
};
});
const observer = a().subscribe(
value => {
console.log('Subscription');
observer.unsubscribe();
},
e => console.log(e),
() => console.log('complete')
);
But the following code is working
const b = (): Observable<any> =>
new Observable(sub => {
setTimeout(()=>sub.next(1),0);
return () => {
console.log('unsubscribe');
};
});
const observer2 = b().subscribe(
value => {
console.log('Subscription b');
observer2.unsubscribe();
},
e => console.log(e),
() => console.log('complete')
);
Help me understand the reason behind it
as you mentioned in the title of your question, the first example is synchronous, so you get the first value while still inside of the .subscribe() method. Naturally, observer, which is supposed to have a Subscription object hasn't been initialized yet.
If you want to unsubscribe after receiving a single value I would suggest to use .take(1)
I have two observable streams which do very separate mapping logic, but then ultimately end with following 3 operators:
this.selection
.pipe(
..Custom mapping operators
tap(_ => this.devicesLoading = true),
switchMap(d => this.mapService.findLocationForDevices(d)),
map(loc => marker([loc.latitude, loc.longitude])
)
.subscribe(markers => this.plotMarkers(markers));
I want to move the last tap, switchMap, map operators to a common function so I can just apply these within both of my observable streams.
I thought of doing:
private resolveLocationsAndConvertToMarkers = (devices: String[]) => [
tap(_ => this.devicesLoading = true),
switchMap((devices: string[]) => this.mapService.findLocationForDevices(devices)),
map(loc => marker([loc.latitude, loc.longitude])
];
But I wasn't sure how to spread these operators into the pipe arguments, like:#
this.selection
.pipe(
// Custom mapping operators
... this.resolveLocationsAndConvertToMarkers
)
.subscribe(markers => this.plotMarkers(markers));
this errors that there are no overloads that expect 3 or 5 arguments..
You can try use native .apply()
this.selection
.pipe.apply(null,this.resolveLocationsAndConvertToMarkers)
or wrap the list of operator in pipe()
private resolveLocationsAndConvertToMarkers = (devices: String[]) => pipe(
tap(_ => this.devicesLoading = true),
switchMap((devices: string[]) => this.mapService.findLocationForDevices(devices)),
map(loc => marker([loc.latitude, loc.longitude])
);
or return higher order function
private resolveLocationsAndConvertToMarkers = (devices: String[]) => source=>source.pipe(
tap(_ => this.devicesLoading = true),
switchMap((devices: string[]) => this.mapService.findLocationForDevices(devices)),
map(loc => marker([loc.latitude, loc.longitude])
);
You could try a reactive approach (with no side effects unless really isolated):
const preSelection$ = this.selection
.pipe
//..Custom mapping operators
();
const selection$: Observable<Marker> = preSelection$.pipe(
switchMap(preSelection =>
concat(
of(null),
of(preSelection).pipe(
switchMap(d => this.mapService.findLocationForDevices(d)),
map(loc => marker([loc.latitude, loc.longitude]))
)
)
),
shareReplay({ bufferSize: 1, refCount: true })
);
const isLoading$: Observable<boolean> = selection$.pipe(map(x => !!x));
const sideEffectUpdatePlotMarkers$ = selection$.pipe(
tap(markers => this.plotMarkers(markers))
);
// isolate `subscribe` calls and side effects as much as possible
sideEffectUpdatePlotMarkers$.subscribe();
I'm hoping this answer will help anyone else who stumbles across this question. The accepted answer did not exactly work for me, with the primary reason being null was passed as the first parameter for .apply() instead of my observable function again. Here is an example similar to what I successfully implemented in my project.
private pipeActions = [
filter(...),
map(...),
];
private myObservable = combineLatest(...);
doThing(): Observable<any> {
return this.myObservable
.pipe.apply(this.myObservable, [...this.pipeActions]);
}
doOtherThing(): Observable<any> {
return this.myObservable
.pipe.apply(
this.myObservable,
[...this.pipeActions, map(...)], // Do something additionally after my list of pipe actions
);
}
I have an observable of an array of tasks. I want to filter these tasks by another observable (a text input).
Then I want to group the filtered tasks by an 'entity' property and map that so the result is:
{ 'entity': 'a',
'tasks': [...] },
{ 'entity': 'b',
'tasks': [...] }
This is the closest test i've got:
https://stackblitz.com/edit/rxjs-filter-group
But I can't seem to group them correctly..
I've updated part of your code, pls, see:
const tasks = combineLatest(source,this.searchQuery$).pipe(
map(([tasks,query]) => {
return tasks.filter(task => {
return task.name.toLowerCase().indexOf(query.toLowerCase()) > -1;
})
}),
mergeMap(v => from(v)
.pipe(
groupBy(task => task.entity),
mergeMap(group => group.pipe(toArray()))
)
)
)