Search code examples
angulartypescriptrxjs

How can I retrieve values from multiple FormControl instances in Angular using RxJS?


I subscribe to filter1, filter2, and filter3, all of which are FormControl instances. Whenever at least one of them changes, I want to retrieve the values of all three. Initially, I attempted to use combineLatest, but I noticed that it emits only when all of them have emitted values. Then, I tried using merge, but encountered an issue where it only returned the field that changed.

So, how can I obtain an object { filter1, filter2, filter3 } using RxJS?

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { merge } from 'rxjs';
import { map } from 'rxjs/operators';
console.clear();

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [FormsModule, ReactiveFormsModule, CommonModule],
  template: `
 <input [formControl]="filter1">
 <input [formControl]="filter2">
 <input [formControl]="filter3">
  `,
})
export class App {
  name = 'Angular';

  filter1 = new FormControl();
  filter2 = new FormControl();
  filter3 = new FormControl();

  constructor() {
    merge(
      this.filter1.valueChanges.pipe(map(value => ({ filter1: value }))),
      this.filter2.valueChanges.pipe(map(value => ({ filter2: value }))),
      this.filter3.valueChanges.pipe(map(value => ({ filter3: value })))
    ).subscribe((x) => {
      console.log(x);
    });
  }
}

bootstrapApplication(App);

Please note that I don't want to use formGroup and I want to know how to do it without use formGroup.

stackblitz


Solution

  • The operator combineLatest is the correct approach, but you need to use the startWith operator for each observable first to provide an initial value. If you don't want the initial value with all filters empty, you can also pipe the result of combineLatest through skip(1).

    import { Component } from '@angular/core';
    import { ReactiveFormsModule, FormControl } from '@angular/forms';
    import { bootstrapApplication } from '@angular/platform-browser';
    import { startWith, combineLatest } from 'rxjs';
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [ReactiveFormsModule],
      template: `
        <input [formControl]="filter1" />
        <input [formControl]="filter2" />
        <input [formControl]="filter3" />
      `,
    })
    export class AppComponent {
      filter1 = new FormControl();
      filter2 = new FormControl();
      filter3 = new FormControl();
    
      constructor() {
        combineLatest({
          filter1: this.filter1.valueChanges.pipe(startWith('')),
          filter2: this.filter2.valueChanges.pipe(startWith('')),
          filter3: this.filter3.valueChanges.pipe(startWith('')),
        }).subscribe((x) => {
          console.log(x);
        });
      }
    }
    
    bootstrapApplication(AppComponent);