[Fixed] Angular – Reactive Form: How to correctly get values of dynamical and reusable sub-forms

Issue

I am working on a form as below

enter image description here

enter image description here

My code

nested-form.component.ts

public nestedForm: FormGroup;
constructor(
    private formBuilder: FormBuilder) {
    this.calibrationForm = this.buildFormGroup(formBuilder);

  }

buildFormGroup(formBuilder: FormBuilder): FormGroup {
    return  new FormGroup({
      motor: new FormControl('', Validators.required),
      properties: new FormControl(""),
    });
  }

save() {
    console.log(this.nestedForm.value);
}

nested-form.component.html

<form [formGroup]="nestedForm" >

    <mat-select class="field-select" [(ngModel)]="_selectedMotor"
                    (selectionChange)="selectMotor($event.value)" formControlName="motor">
                    <mat-option class="select-option" *ngFor="let item of motors" [value]="item">
                        item</b>
                    </mat-option>
                </mat-select>

    <app-properties formControlName="properties" [type] = "_selectedMotor"></app-properties>
   ...
</form>

properties.component.ts

@Component({
  ...
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => PropertiesComponent),
      multi: true
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => PropertiesComponent),
      multi: true
    }
  ]
})

export class PropertiesComponent implements OnInit, ControlValueAccessor {
    @Input() type: string = null;
    public propertiesForm: FormGroup;
    ngOnInit(): void {
        this.propertiesForm = this.buildFormGroup(this.formBuilder);
    }

    ngOnChanges(changes): void {
        this.propertiesForm = this.buildFormGroup(this.formBuilder);
    }

    buildFormGroup(formBuilder: FormBuilder): FormGroup {
        switch (this.type) {
          case "OLD":
           
            return new FormGroup(
              {
                tilt: new FormControl(0, [Validators.required]),
                fine_tilt: new FormControl(0, [Validators.required]),
                rotate: new FormControl(0, [Validators.required]),
                fine_rotate: new FormControl(0, [Validators.required])
              });

          case "VINTEN":
           
            return new FormGroup(
              {
                vinten_tilt: new FormControl(0, [Validators.required]),
                vinten_rotate: new FormControl(0, [Validators.required])

              });
        }
     }

  public onTouched: () => void = () => { };
  writeValue(val: any): void {
    val && this.propertiesForm.setValue(val, { emitEvent: false });
  }
  registerOnChange(fn: any): void {
    this.propertiesForm.valueChanges.subscribe(fn);
  }
  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
  setDisabledState?(isDisabled: boolean): void {
    isDisabled ? this.propertiesForm.disable() : this.propertiesForm.enable();
  }

  validate(c: AbstractControl): ValidationErrors | null {
    return this.propertiesForm.valid ? null : { invalidForm: { valid: false, message: "propertiesForm fields are invalid" } };
  }
  ...
}

properties.component.html

<form *ngIf="type" class="col" [formGroup]="propertiesForm" [ngSwitch]="type">
    <div *ngSwitchCase="OLD">
        <input type="number" class="field-input" id="rotate" formControlName="rotate" />
        <input type="number" class="field-input" id="fine_rotate" formControlName="fine_rotate" />
        <input type="number" class="field-input" id="tilt" formControlName="tilt" />
        <input type="number" class="field-input" id="fine_tilt" formControlName="fine_tilt" />
    </div>

    <div *ngSwitchCase="VINTEN">
        <input type="number" class="field-input" id="rotate" formControlName="vinten_rotate" />
        <input type="number" class="field-input" id="tilt" formControlName="vinten_tilt" />
    </div>
</form>

Result:

Let’s say OLD is a default, when the form is loaded.

I entered all fields and click Save button for OLD, the form values is collected correctly as expected.

However, when I switched from OLD to VINTEN, the form UI is updated but valid/invalid state of Save button is not working any more, and after clicking Save button, the form values are still the OLD properties values, not VINTEN properties values.

Did I miss something or make something wrong? Any suggestion is appreciated.

Solution

To notify parent control about internal changes you have to listen propertiesForm.valueChanges().

And call fn that you were given by registerOnChange(fn: any): void

onModelChange = () => {}


 ngOnInit(): void {
    this.propertiesForm = this.buildFormGroup(this.formBuilder);
    this.propertiesForm.valueChanges().subscribe(value => this.onModelChange(value))
}

registerOnChange(fn: any): void {
  this.onModelChange = fn;
}

Angular forms

Leave a Reply

(*) Required, Your email will not be published