How to update a nested FormGroup passed as input to a child component in Angular?

194 Views Asked by At

I have trouble passing the nested FormGroup (the one that is a FormControl of an outer FormGroup) as an input to a child component. More precisely, the changes made to the form group are reflected in it (I can log the expected changed form group both from parent and child; valueChanges subscription works as well), but change detection never fires.

Here is the minimal reproduction:

parent.component.ts

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

@Component({
  template: '<app-child [inputGroup]="nestedGroup"></app-child>',
})
export class AppComponent implements OnInit {
  form: FormGroup;

  constructor(private fb: FormBuilder) {}

  get nestedGroup() {
    return this.form.get('nested') as FormGroup;
  }

  ngOnInit(): void {
    this.form = this.fb.group({
      flag: [true],
    });

    this.form.setControl(
      'nested',
      this.fb.group({ field1: { key: 'testString' } })
    );

    this.form.valueChanges.subscribe((form) =>
      console.log('Form updated in parent!', form)
    );

    setTimeout(() => this.changeValue(this.nestedGroup), 5000);
  }

  changeValue(nestedForm: FormGroup) {
    console.log('Should log CHILD FORM changes now:');
    nestedForm.controls['field1'].patchValue({ key: 'newTestString' });
  }
}

child.component.ts

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

@Component({
  selector: 'app-child',
  template: '<p>child showing up!</p>',
})
export class ChildComponent implements OnChanges, OnInit {
  @Input()
  inputGroup: FormGroup;

  constructor(private cdr: ChangeDetectorRef) {}

  ngOnChanges(changes: SimpleChanges) {
    console.log('Form updated in child!', changes); // TOFIX: should run after 5 sec
  }

  // the code below is only to illustrate that the changes are there
  ngOnInit() {
    this.inputGroup.valueChanges.subscribe((value) => {
      console.log('Got valueChanges in child...', value);
      this.cdr.detectChanges();
    });
  }
}

There is also a small stackblitz I made.

I tried explicitly marking form group as dirty (formGroup.markForDirty()) and firing change detection manually on both levels (cdr.markForCheck(), cdr.detectChanges()).

I expected ngOnChanges to react to a new input. Potentially I would like to manipulate and react to the input FormGroup values in the child.

The result was an active input valueChanges subscription, but not an input change.

1

There are 1 best solutions below

0
On BEST ANSWER

ngOnChanges will only be called if the object instance of your inputGroup changes. If just a value inside the object changes, it is not enough.

But as you noticed, you do get the changes, and subscribing to valueChanges is exactly the way to go.

For the terminology: Change detection is actually done on your component. If you print the form control value in your template, you should see that it updates after the patchValue call. That means, Angular detected the change and updated the view. But change detection does not mean that ngOnChanges gets called.