I am having problems trying to concat two different observables and show the results into a Material Select component.
On this example (built using the tool from Material docs) there is a simple example simulating what I want to do. Options are not being shown.
In my current code (on my machine), the problem I see is: the first Observable that is passed to the concat operator is subscribed and all items are shown in the Select. But I belive that the first Observable never completes. It looks like material-select is not "completing" the first Observable and, because of that, concat won't subscribe to the next Observable while the first one never completes, as it says in its docs:
Note that if some input Observable never completes, concat will also never complete and Observables following the one that did not complete will never be subscribed.
What I need is this: taking two different Observables (with different data in each one), concat them (or any other operation), and, in the end, show all their data combined as options of a single select component.
Just as a reference, I'll copy the code here also, so it won't be lost in the future:
// Component:
import { Component, Input } from '@angular/core';
import { from } from 'rxjs';
import { concatWith } from 'rxjs/operators';
interface Option {
value: number;
label: string;
}
@Component({
selector: 'select-overview-example',
templateUrl: 'select-overview-example.html',
})
export class SelectOverviewExample {
@Input()
options$: any;
constructor() {}
ngOnInit() {
// Let's pretend this data comes from API 1
const sourceA$ = from([
{ value: 1, label: 'Value 1' },
{ value: 2, label: 'Value 2' },
{ value: 3, label: 'Value 3' },
]);
// And these ones comes from API 2
const sourceB$ = from([
{ value: 4, label: 'Value 4' },
{ value: 5, label: 'Value 5' },
{ value: 6, label: 'Value 6' },
]);
// Now, concat the data from both observables and display all of them as options in a Select component:
this.options$ = concatWith(sourceA$, sourceB$);
}
}
And the template:
<h4>Basic mat-select</h4>
<mat-form-field appearance="fill">
<mat-label>Select some value:</mat-label>
<mat-select>
<mat-option
*ngFor="let option of (options$ | async)"
[value]="option.value"
>
{{option.label}}
</mat-option>
</mat-select>
</mat-form-field>
CodePudding user response:
You can collect the values from multiple observables using the merge function and the scan operator as follows:
import { from, merge } from 'rxjs';
import { scan } from 'rxjs/operators';
const sourceA$ = from(/* ... */);
const sourceB$ = from(/* ... */);
merge(sourceA$, sourceB$)
.pipe(
scan((acc, value) => {
acc.push(value);
return acc;
}, [])
)
.subscribe((value) => console.log(value));
In this example, you can replace merge
with concat
and it still works. If the order matters and you want to get all values from sourceA$
before subscribing to sourceB$
, then you are correct that concat
should be used instead of merge
.
Regarding your comment...
It looks like material-select is not "completing" the first Observable
It is not the responsibility of material-select to complete the source Observable. It's the responsibility of the source Observable to complete. material-select has no way of knowing when the source Observable is done emitting values without the source Observable completing itself.
CodePudding user response:
Using forkJoin
and then merge the two array should be a good and clean option:
this.options$ = forkJoin([sourceA$, sourceB$]).pipe( // runs both parallel
map(res => {
const sA = res[0];
const sB = res[1];
return [...sA, ...sB]; // merges two array using the spread operator
})
);