Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change detection does not trigger when the formgroup values change

I have created a simple example to demonstrate a weird issue I'm facing.

Stackblitz - https://stackblitz.com/edit/angular-change-detection-form-group

I have three components and here are they:

1 - app component

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'my-app',
  template: `<hello [form]="form"></hello>
  <hr />
  <button (click)="changeFormValue()">Change Form Value</button>`,
  styleUrls: ['./app.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush

})
export class AppComponent implements OnInit {
  name = 'Angular';

  form: FormGroup;

  ngOnInit() {
    this.form = new FormGroup({
      name: new FormControl('ABC'),
      age: new FormControl('24')
    });
  }

  changeFormValue() {
    this.form.setValue({
      name: 'XYZ',
      age: 35
    })
  }
}

2 - hello component

import { Component, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
  selector: 'hello',
  template: `<form [formGroup]="form">
  <app-input [form]="form"></app-input>
  </form>`,
  styles: [``],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class HelloComponent implements OnChanges {
  @Input() form: FormGroup;

  ngOnChanges(changes) {
    console.log(changes)
  }
}

3 - input component

import { Component, Input, OnInit, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
  selector: 'app-input',
  template: `Name : <input type="text" [formControl]="nameFormcontrol" /> {{nameFormcontrol.value}} <br /><br />
  Age : <input type="text" [formControl]="ageFormcontrol" /> {{ageFormcontrol.value}}`,
  styles: [``],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class InputComponent implements OnInit, OnChanges {
  @Input() form: FormGroup;
  nameFormcontrol;
  ageFormcontrol;

  ngOnInit() {
    this.nameFormcontrol = this.form.get('name');
    this.ageFormcontrol = this.form.get('age');
  }

  ngOnChanges(changes) {
    console.log(changes)
  }
}

In both hello component and input component, I have set the changedetection strategy to onpush. As you see above I am creating a form group instance in the app component and passing that to the child components. Now when I click on the button on app component to change the form value, it changes the value in the input fields but not the plain texts. It only works if I delete the on push change detection from both the child components. Even the ngOnChanges does not get called even if the formgroup values are changing.

enter image description here

Is n't it weird. How the change detection works for inputs and not for the plain texts here?

Could someone explain me this please? And what is the workaround of it without removing the onpush change detection.

like image 826
Ansuman Avatar asked Dec 05 '18 20:12

Ansuman


4 Answers

I found a workaround to this problem although I am not sure if this is the ideal solution.

We can listen to the form group value changes and then trigger change detection in the input component

this.form.valueChanges.subscribe( () => {
  this.cdr.detectChanges()
});

This way it updates the label values as well along with the inputs.

enter image description here

Here is the solution:

https://stackblitz.com/edit/angular-change-detection-form-group-value-change-issue-resolved

I'm not sure if it's a bug from Angular but happy that I figured out some workaround :)

like image 158
Ansuman Avatar answered Nov 11 '22 01:11

Ansuman


You could do something better !, let me know if this works :

when you use reactive forms you can use a really cool method called updateValueAndValidity();

private changeControlValue(control, value: number) {
    control.setValue(value);
    control.updateValueAndValidity();
  }

You can also use this method when updating the validators added to a form, example:

this.control.setValidators([Validators.required, Validators.minLength(5)]);
control.updateValueAndValidity();

This should do the trick ! I think this is one of the best adventages of using reactive forms or form controls against ng-model.

I don't recommend to use valueChanges at your form as you are open to forget to de-subscribe and create a memory leak, even you remember it can be tedious to create this flow.

And remember, when using onPush change detection only three things are going to be detected as changes by Angular:

1- Inputs and Outputs. 2- Html events that the user can do, like (click). 3- Async events like subscriptions.

I hope i helped you !.

like image 5
Alan Buscaglia Avatar answered Nov 11 '22 01:11

Alan Buscaglia


Simply subscribe to the control's valueChanges property in the template using the async pipe, avoiding the need for manually triggering the change detection and subscribing to valueChanges in the component.

<input [formControl]="control"/>
<p>{{control.valueChanges | async}}</p>
like image 2
Rafi Henig Avatar answered Nov 11 '22 00:11

Rafi Henig


Angular only detects changes if the memory address of the variable changes. Setting the value does not change memory address, thus does not hit ngOnChanges.

Same goes with arrays. A simple push does not hit ngOnChanges, have to change memory address by = to new array.

Try this:

import { Component, Input, OnInit, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
  selector: 'app-input',
  template: `
  <div formGroupName="form">
      Name : <input type="text" formControlName="name" /> {{form.value.name}} <br /><br />
      Age : <input type="text" formControlName="age" /> {{form.value.age}}
  </div>`,
  styles: [``],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class InputComponent implements OnInit, OnChanges {
  @Input() form: FormGroup;

  ngOnInit() {
  }

  ngOnChanges(changes) {
    console.log(changes)
  }
}
like image 1
user1779362 Avatar answered Nov 11 '22 01:11

user1779362