Creating a nested form made up of the following "nested object" (Building):
export class Building{
id: number = 0;
doorsCount: number = 0;
description: string = '';
address: Address = new Address();
buildingType: BuildingType = new BuildingType();
}
export class Address{
id: number = 0;
description: string = '';
}
export class BuildingType{
id: number = 0;
description: string = '';
}
As you can see Building
class contains other classes like Address
and BuildingType
which also have other properties such as id
and description
.
When creating the form I used the following code at the component ts file:
buildingForm: FormGroup;
construct(private fb: FormBuilder){
this.buildingForm = this.createBuildingFG(new Building);
}
createBuildingFG(building: Building){
let formGroup : FormGroup;
formGroup = this.fb.group(building);
// Because object of type "building" contain properties of non-primitive
// types such as object Address and BuildingType I think the following
// additional lines are required.
formGroup.controls.address = this.fb.group(building.address);
formGroup.controls.buildingType = this.fb.group(building.buildingType);
return formGroup;
}
And this is how the form is bound to the HTML template:
<form [formGroup]="buildingForm">
<label>
Door count:
<input formControlName="doorsCount" >
</label>
<label>
Building description:
<input formControlName="description" >
</label>
<label formGroupName="address">
Address:
<input formControlName="description" >
</label>
<label formGroupName="buildingType">
Building type:
<input formControlName="description" >
</label>
</form>
Now the problem arises when I output the value of the whole form which do not really update according to what is being typed in the nested field controls that are inside a formGroup
such as address or buildingType. Otherwise it updates normally.
This is how the value is output
<div>
{{buildingForm.value | json}}
</div>
However, if createBuildingFG
function was done differently and every formControl
was created explicitly with out just passing an entire object, the form acts normally. Example:
createBuildingFG(building: Building){
let formGroup : FormGroup;
formGroup = this.fb.group({
doorsCount: '',
description: '',
address: this.fb.group({ description: ''}),
buildingType: this.fb.group({ description: ''})
});
return formGroup;
}
Anyone can explain what is going on? Obviously to avoid doing this tedious task of explicitly defining each element of the fromGroup
, one would want to just pass an entire object.
As @jonrsharpe mentioned in comments
because the initial construction does things you're not subsequently overriding.
So what are the things?
When angular create new instance of FormGroup it calls _setUpControls
method
export class FormGroup extends AbstractControl {
constructor(
public controls: {[key: string]: AbstractControl},
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null) {
super(
coerceToValidator(validatorOrOpts),
coerceToAsyncValidator(asyncValidator, validatorOrOpts));
this._initObservables();
this._setUpdateStrategy(validatorOrOpts);
this._setUpControls(); <--------------
and now let's look at the method:
/** @internal */
_setUpControls(): void {
this._forEachChild((control: AbstractControl) => {
control.setParent(this);
control._registerOnCollectionChange(this._onCollectionChange);
});
}
As we can see each item of controls sets parent and registers some event but you don't do that.
The following code should work:
formGroup = this.fb.group(building);
formGroup.controls.address = formGroup.controls.buildingType = null;
formGroup.registerControl('address', this.fb.group(building.address));
formGroup.registerControl('buildingType', this.fb.group(building.buildingType));
or you can use recursion to do it working:
constructor(private fb: FormBuilder){
this.buildingForm = this.createFormGroup(new Building);
}
createFormGroup(obj: any) {
let formGroup: { [id: string]: AbstractControl; } = {};
Object.keys(obj).forEach(key => {
formGroup[key] = obj[key] instanceof Object ? this.createFormGroup(obj[key]) : new FormControl(obj[key]);
});
return this.fb.group(formGroup);
}