Home > database >  RxJs: concat two observables as options for material select
RxJs: concat two observables as options for material select

Time:02-11

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

StackBlitz Example

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