angular: Reactive Form change through ControlValueAccessor doesn't update view

I’m submitting a … (check one with “x”)

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior When updating a value through a form field that uses the formControl directive, the view isn’t updated. When, say, two fields hold the same form control, changing one does NOT change the other.

This does happen when using NgModel, and does happen when calling formControl.setValue(newVal)

Expected behavior Behaviour is expected to be the same in NgModel as when using Reactive FormControls

Minimal reproduction of the problem with instructions http://plnkr.co/edit/LXyo6uHdbEpP3feudYoo?p=preview

What is the motivation / use case for changing the behavior? The contract custom form fields have when using the ControlValueAccessor interface is broken. A good example is http://valor-software.com/ng2-bootstrap/#/buttons#radio - which works currently only with NgModel, as it requires updating the view on all relevant buttons on value change.

When using a set of buttons with reactive forms, currently, a (click) based hack needs to be put in place:

<div class="btn-group">
    <label 
        class="btn btn-primary" 
        formControlName="privateApplication"
        (click)="form.get('privateApplication').setValue(false)"
        [btnRadio]="false">
            {{'LABEL_PUBLIC' | translate}}
    </label>
    <label 
        class="btn btn-primary" 
        formControlName="privateApplication" 
        (click)="form.get('privateApplication').setValue(true)"
        [btnRadio]="true">
            {{'LABEL_PRIVATE' | translate}}
    </label>
</div>

Please tell us about your environment: OS Not relevant

  • Angular version: 2.4.1

  • Browser: Chrome 55

  • Language: TypeScript 2

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 62
  • Comments: 57 (2 by maintainers)

Most upvoted comments

For those who came from google with this issue like me, there’s a workaround that saved me:

control.valueChanges.subscribe(e => {
    control.setValue(e, {emitEvent: false});
});

Hello ? Can you fix major 2 years old bugs ?
This is a total blocker when implementing custom form component.

still present with 7.3.4

@Ledzz solution works, but be aware of the jumping cursor in IE11: ie11-typing Try it: https://plnkr.co/edit/ilRx7V7VfNbzgB2lqC1v?p=preview

I need a input field and a select to work on the same control. Following workaround worked for me:

<form [formGroup]="form">
  <input type="text" formControlName="bewarage">
  <select formControlName="bewarage" #s
      [value]="form.get('bewarage').value"
      (change)="form.get('bewarage').setValue(s.value, {emitEvent: false})">
    <option *ngFor="let b of bewarages" value="{{b}}">{{b}}</option>
  </select>
</form>

try it: https://plnkr.co/edit/WaLHOlcNAVbSAH8m0a7o?p=preview

On topic: In my opinion this is an unexpected behaviour, so I see it as a bug not a missing feature.

Almost 4 years for this bug… 👎

I have a stepper in my app and last step is a summary of previous forms. Well I have to use same form groups at the last step, but as it is formcontrols duplication summary is not updated anytime

This worked for me for both a reactive form and a non-reactive one:

import {ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input} from "@angular/core";
import {Observable} from "rxjs/Observable";
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from "@angular/forms";
...

@Component({
	selector: 'my-select',
	changeDetection: ChangeDetectionStrategy.OnPush,
	templateUrl: './outcome-select.template.html',
	styleUrls: ['./outcome-select.scss'],
	providers: [
		{
			provide: NG_VALUE_ACCESSOR,
			useExisting: forwardRef(() => OutcomeSelectComponent),
			multi: true
		}
	]
})
export class OutcomeSelectComponent implements ControlValueAccessor {
	// For more on the style of this component, see: https://alligator.io/angular/custom-form-control/
	@Input() isRequired: boolean;
	@Input() noSelectionText: string;
	@Input('value') _value: number; // outcome id
	onChange: any = () => { };
	onTouched: any = () => { };

	options$: Observable<OutcomeOption[]>;

	get value(): number {
		return this._value;
	}

	set value(val: number) {
		this._value = val;
		this.onChange(val);
		this.onTouched();
		this.cd.detectChanges();
	}

	constructor(
		private cd: ChangeDetectorRef
	) {
		this.options$ = Observable.of([
			{
				name:'Option 1',
				id: 1
			}, {
				name: 'Option 2',
				id: 2
			}
		]);
	}

	registerOnChange(fn) {
		this.onChange = fn;
	}

	registerOnTouched(fn) {
		this.onTouched = fn;
	}

	writeValue(value: number) {
		this.value = value;
	}
}

The template looks like this:

<select
		[required]="isRequired"
		[(ngModel)]="value">
	<option value="" [disabled]="isRequired">{{ noSelectionText }}</option>
	<option [ngValue]="opt.id" *ngFor="let opt of (options$ | async)">{{opt.name}}</option>
</select>

This is not a bug. When changing a class variable via the writeValue method it is a change which is not detected via zone.js or via an Input change. You need to manually trigger the ChangeDetectorRef detectChanges method to let Angular synchronize the class’s instance properties with the DOM, or use the Renderer2 to change the native element value manually as stated in the docs .

I’m having the same problem. When I do this from my controller:

this.myform.setValue({
	someLabel: somevalue
});

With this template:

<form [formGroup]="myform" novalidate>
   <custom-component formControlName="someLabel"></custom-component>
</form>

My custom component that implements ControlValueAccessor, never gets the new value passed to it’s writeValue method. There appear to be no workarounds.

I use a painful hack, maybe you cant use this solution always, I don’t know. template: <input type="text" (input)="inputEventHandler($event)" #tstInput > component: @ViewChild('tstInput ') tstInput;

writeValue(value: any) {
    if (!value) {
      this.tstInput.nativeElement.value = null;
    } else {
      this.tstInput.nativeElement.value = value;
    }
}

If you only have a subset of form fields you know to be ‘duplicated’ you’re slightly better off using patchValue instead.

 this.addressForm.valueChanges.subscribe((data) => {
        this.addressForm.patchValue({ firstName: data.firstName, lastName: data.lastName }, 
                                    { emitEvent: false });
  });

This will probably mitigate issues with browser controls quirks such as the above mentioned IE11 issue.

Still strikes me as bizarre this is the ‘by design’ behavior. I suppose it is a performance issue. Everything in angular magically binds - except for the simplest forms 😕

Sorry, ignore the above. I reread your issue see whats going on here, I don’t know the inner workings overly well but it looks to me like a limitation of Reactive Forms currently not necessarily a bug. Maybe opening a Feature request for this after checking if there isn’t one already

This needs some serious looking. Is there any update on whether or not this is being looked into?

Thought I was really missing something in my app… And decided to make a small repro before going insane.

Turns out it’s a real issue.

If anyone ask for a use case, just imagine a color picker, like the one integrated in Chrome debug tool.

image

You can either change the color from the input or change it from the color palette. Both are same formControlName but with different display 🤷‍♂️

Another solution that worked for me, using timeout, bit hacky but does a job, just wrap the patchValue or setValue with timeout

  setTimeout(() => {
        this.complexSearchForm.patchValue({
          complexSearchQueryCtrl: ruleSet,
          filterTagsCtl: {
            filterTags: obj.tags ? true : false,
            selectedTagTypes: obj.tags && obj.tags.required ? obj.tags.required : [],
            excludedTagTypes: obj.tags && obj.tags.excluded ? obj.tags.excluded : []
          },
        });
      }, 0);

While this works definitely, it’s not optimal as Angular needs another ChangeDetection cycle to pick up your changes. Did you try to call changeDetectorRef.detectChanges() after the patchValue?

Happened to me when I had the following in the NG_CONTROL_VALUE_ACCESSOR:

image

commenting out the .reset() code solved it

I use google search. When I select a place (by enter or by mose’s click), (in code I call FormGroup.patchValue()) Inputs were filled, but view not rendered. View rendered when I do something with browser, even just do click.

angular: v. 5.2.9

please look to attached screens

{emitEvent: false} does not help me…

image image image image

There are two points to note about the following code: http://plnkr.co/edit/LXyo6uHdbEpP3feudYoo?p=preview

First, it’s worth to say that in this example: there is a subtle difference between the template driven and reactive versions:

  • In the reactive version, there is a single FormControl object.
  • But in the template driven version there are two FormControl objects.

We can avoid this difference by wrapping the inputs in a form.

Second, the two versions (template driven and reactive) are not really equivalent. And the problem (if it’s really a problem), exists also in the template driven forms as in the reactive ones. Here http://plnkr.co/edit/mPDLG7MZlp6KRvkYBN89?p=preview I give the equivalent reactive version of the template driven one and the template driven version equivalent to the reactive one.