1

I have a dynamic array of ng-select controls. Each control represented by class NgSelectComponent.

When select value changes I want to subscribe to all controls.

Template

  <ng-select #select">
    <ng-option *ngFor="let option of options" [value]="select.id">{{ option.name }}</ng-option>
  </ng-select>

Class

  @ViewChildren('select') controls: QueryList<NgSelectComponent>;

  ngAfterViewInit() {
    concat(this.controls.toArray()).subscribe(x => {
      console.log(x);
    });
  }

I try that, but does not work.

concat(this.components.toArray()).subscribe(x => {
  console.log(x);
});

I believe it does not work because I had to subscribe to the values produced by each control corresponded by changeEvent but struggling to do that.

Any ideas how to solve?

Sergino
  • 10,128
  • 30
  • 98
  • 159

2 Answers2

2

This is what Reactive Forms are for. You create a FormArray and have FormControls in there. Then you can subscribe to the valueChanges observable of the FormControls. I highly recommend you to rewrite the code using this angular feature.

That being said, the easiest and more let's say unrefined solution is to use the changeEvent as you said:

<ng-select #select (change)="selectChanged($event)">
    <ng-option *ngFor="let option of options" [value]="select.id">{{ option.name }}</ng-option>
  </ng-select>
selectChanged($event) {
  // This function is called every time any select is changed.
  // If you want, you can create a Subject and .next() the values there to have an Observable
  console.log($event.target.value);
}
Garuno
  • 1,935
  • 1
  • 9
  • 20
  • it seems following this approach you have to use dynamic forms approach https://angular.io/guide/dynamic-form ? – Sergino Nov 02 '22 at 12:43
  • Yeah, that approach is probably perfect for your use-case. – Garuno Nov 02 '22 at 12:50
  • 1
    yes, thank you for suggestion, I will +1 that and might try that later but accepting the other answer as it was helpful and works – Sergino Nov 02 '22 at 13:10
2

First of all, concat will likely not work. With concat, each observable in the array will wait for the previous to complete. This is probably not the behaviour you are expecting here.

You may use merge which will simply forwards all the emitted values to the output Observable individually. Or use combineLatest if you want an array of all the current values, whenever one Observable emits.

To convert the valueChanges of the controls to an array, use a map.

merge(
  ...this.controls.toArray().map(c => c.changeEvent.asObservable())
).subscribe(x => {
  console.log(x);
});
Tobias S.
  • 21,159
  • 4
  • 27
  • 45
  • seems `valueChanges` does not exist in there, I try `changeEvent` but does not work even trying that with `merge` – Sergino Nov 02 '22 at 12:37
  • it specified in the question, here it is https://github.com/ng-select/ng-select/blob/master/src/ng-select/lib/ng-select.component.ts#L73 – Sergino Nov 02 '22 at 12:40
  • it seems that `changeEvent` is the one, but no luck – Sergino Nov 02 '22 at 12:41
  • @sreginogemoh - I edited my answer. Can you try that? – Tobias S. Nov 02 '22 at 12:43
  • just did, all same..., I put custom `(change)` event to debug just to see if controls does trigger single the changes, and it does, but this approach does not.. – Sergino Nov 02 '22 at 12:46
  • also if I do this, it works `this.controls.toArray()[0].changeEvent.subscribe(console.log())` – Sergino Nov 02 '22 at 12:49
  • did you try it with `merge` instead of `concat` ? – Tobias S. Nov 02 '22 at 12:50
  • yes, tried both, does not work either of them – Sergino Nov 02 '22 at 12:50
  • I got one more idea. Add the spread operator in the `merge` – Tobias S. Nov 02 '22 at 12:57
  • it does work now using spread operator, I wonder why? – Sergino Nov 02 '22 at 12:59
  • However say I have 3 controls there, so current rxjs code seems return only one latest selected, is there any way to accumulate the values by id so once all set to run the subscribe logic? – Sergino Nov 02 '22 at 13:02
  • The `merge` method does not actually accept an array. It has a *rest parameter*. Thats why spreading works. You could use `combineLatest` (without the spread) to get an array of all values. But the other ones won't be blank. They will have their latest values as well. – Tobias S. Nov 02 '22 at 13:03
  • I guess you would need some sort of state to keep track which have already been selected. – Tobias S. Nov 02 '22 at 13:04
  • oh right, that is interesting, the `combineLatest` seems works exactly like that, accumulating the values, unless all set there is no emit, right? and then once you change one it brings the others previous values as well. – Sergino Nov 02 '22 at 13:05
  • @sreginogemoh - yes, exactly – Tobias S. Nov 02 '22 at 13:07
  • I am using `combineLatest` with a spread – Sergino Nov 02 '22 at 13:07
  • yeah, seems like `comineLatest` accepts both arrays and spread arguments. But I think the spread argument is deprecated here. So its better to pass an array – Tobias S. Nov 02 '22 at 13:08
  • I changed to nospread and it works too, I will keep that. thank you for your help – Sergino Nov 02 '22 at 13:08