@cyberthreat as promised, here's a version with ngx-sub-form.
First of all, here's the link of a live demo: https://stackblitz.com/edit/angular-td2iew
Now let's see how it's built:
app.component.ts
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public licenseUpdated(license: License): void {
console.log(JSON.stringify(license, null, 2));
}
}
app.component.html
<app-license-form (licenseUpdated)="licenseUpdated($event)"></app-license-form>
Nothing too fancy here but notice the fact that from this component we absolutely don't care how we get the data of the license. We just want to be warned as soon as there's an update.
license-form.component.ts
@Component({
selector: 'app-license-form',
templateUrl: './license-form.component.html',
styleUrls: ['./license-form.component.css']
})
export class LicenseFormComponent extends NgxRootFormComponent<License> {
@DataInput()
@Input('license')
public dataInput: License | null | undefined;
@Output('licenseUpdated') public dataOutput: EventEmitter<License> = new EventEmitter();
protected getFormControls(): Controls<License> {
return {
name: new FormControl(null, [Validators.required]),
nameAttributes: new FormControl(null, [Validators.required]),
};
}
}
license-form.component.html
<form [formGroup]="formGroup">
<div>
Name<br>
<input type="text" [formControlName]="formControlNames.name">
</div>
<div>
License attributes<br>
<app-license-attributes-form [formControlName]="formControlNames.nameAttributes"></app-license-attributes-form>
</div>
<button class="save" (click)="manualSave()">Save form</button>
(look at your console to see when the form is saved!)
</form>
<div class="values">
Form group values
<pre>{{ formGroupValues | json }}</pre>
</div>
Here, if you're not familiar with ngx-sub-form I'd invite you to read that blog post: https://dev.to/maxime1992/building-scalable-robust-and-type-safe-forms-with-angular-3nf9 and/or the README on the project: https://github.com/cloudnc/ngx-sub-form
The important thing to notice here is that we only care about primitive values and everything else is broken down into sub form components! Also, everything there is type safe (ts and html!) regarding the form.
license-attributes-form.component.ts
interface LicenseAttributesForm {
attributes: NameAttribute[];
}
@Component({
selector: 'app-license-attributes-form',
templateUrl: './license-attributes-form.component.html',
styleUrls: ['./license-attributes-form.component.css'],
providers: subformComponentProviders(LicenseAttributesFormComponent)
})
export class LicenseAttributesFormComponent extends NgxSubFormRemapComponent<NameAttribute[], LicenseAttributesForm> {
protected getFormControls(): Controls<LicenseAttributesForm> {
return {
attributes: new FormArray([], [Validators.required]),
};
}
protected transformToFormGroup(obj: NameAttribute[]): LicenseAttributesForm {
return {
attributes: obj ? obj : [],
};
}
protected transformFromFormGroup(formValue: LicenseAttributesForm): NameAttribute[] | null {
return formValue.attributes;
}
public addAttribute(): void {
(this.formGroupControls.attributes as FormArray).push(
this.createFormArrayControl(
'attributes',
{
index: null,
attribute: null
}
)
);
}
public removeAttribute(index: number): void {
(this.formGroupControls.attributes as FormArray).removeAt(index);
}
public createFormArrayControl(
key: any,
value: any,
): FormControl {
return new FormControl(value, [Validators.required]);
}
}
license-attributes-form.component.html
<div [formGroup]="formGroup">
<button (click)="addAttribute()">Add an attribute</button>
<div
class="attribute"
formArrayName="attributes"
*ngFor="let attribute of formGroupControls.attributes.controls; let i = index"
>
<app-license-attribute-form [formControl]="attribute"></app-license-attribute-form>
<button (click)="removeAttribute(i)">Delete</button>
</div>
</div>
And finally the last one
license-attribute-form.component.ts
@Component({
selector: 'app-license-attribute-form',
templateUrl: './license-attribute-form.component.html',
styleUrls: ['./license-attribute-form.component.css'],
providers: subformComponentProviders(LicenseAttributeFormComponent)
})
export class LicenseAttributeFormComponent extends NgxSubFormComponent<NameAttribute> {
protected getFormControls(): Controls<NameAttribute> {
return {
index: new FormControl(null, [Validators.required]),
attribute: new FormControl(null, [Validators.required]),
};
}
}
license-attribute-form.component.html
<form [formGroup]="formGroup">
<div>
Index<br>
<input type="number" [formControlName]="formControlNames.index">
</div>
<div>
Attribute<br>
<input type="text" [formControlName]="formControlNames.attribute">
</div>
</form>
I'd really encourage you to have a look on Stackblitz and play around with that demo it'll be the easier way to understand and discover I think :)