ibenjelloun
ibenjelloun

Reputation: 7733

Multiple components binding with the same reactive form control update issue

In some situations, we could need to have multiple form components binded to the same form control. And I'm confused by the angular way of handling this case:

When changing one of the components value, the form value is changed but not the other components values.

The workaround I did in this case is to patch the from with the form value, which is ugly:

this.form.patchValue(this.form.value);

Here is a stackblitz demo for my issue, I added the workaround for the input change not the select for better understanding.

Is there an elegant way to handle this case with angular reactive forms?

Upvotes: 22

Views: 24143

Answers (3)

Didier68
Didier68

Reputation: 1323

I looked for a generic solution: I'm using a JSON var "prevData" to remember the previous value and not loop indefinitely:

this.prevData = "";

this.form.valueChanges.subscribe( 
  
  data => {  
    let str = JSON.stringify(data); 
    if (str != this.prevData){
      this.prevData = str;
      this.form.patchValue(data);
    } 
  }
);

Upvotes: 0

DaMightyMouse
DaMightyMouse

Reputation: 822

I solved it by just using AbstractControl valueChanges and setValue APIs. In this approach you don't have to use 2 separate form controls, you can have any number of components bound to the same reactive form control.

this.form.get('formControlName').valueChanges.subscribe(value => {
    this.form.get('formControlName').setValue(value, { onlySelf: true, emitEvent: false, emitModelToViewChange: true });
  }, error => {}, () => { });

The key here is the options param of setValue, this will prevent the recusive execution of valuesChanges until overflow.

{ onlySelf: true, emitEvent: false, emitModelToViewChange: true }

Upvotes: 13

Tomasz Kula
Tomasz Kula

Reputation: 16837

A nice reactive solution which would not require adding any (change) listeners, would be to create two separate name controls, and keep them in sync by subscribing to valueChanges stream.

component.ts

import { Component, NgModule, ViewChild } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: 'app.component.html'
})
export class AppComponent {
  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      nameText: '',
      nameSelect: '',
    })

    const text = this.form.get('nameText');
    const select = this.form.get('nameSelect');

    text.valueChanges.subscribe(v => select.setValue(v, { emitEvent: false }));
    select.valueChanges.subscribe(v => text.setValue(v, { emitEvent: false }));
  }

}

component.html

<form [formGroup]="form">
  When you change the input, the select changes : <br>
  <input type="text" formControlName="nameText"><br>
  When you change the select, the input does not change : <br>
  <select formControlName="nameSelect">
    <option value="value1">value1</option>
    <option value="value2">value2</option>
  </select>
</form>

Live demo

Upvotes: 21

Related Questions