Utilizing the dynamic form pattern in Angular has been incredibly helpful for our team. By defining our controls in ngOnInit
, the form is dynamically constructed based on our needs. However, we've encountered a challenge with forms that require initialization of values, especially when fetching data asynchronously.
The issue arises when attempting to initialize async data with the dynamic form. Errors are thrown in the console and the form fails to display properly in the view.
In an attempt to address this issue, I incorporated asynchronous functions within the ngOnInit
method as shown below:
async ngOnInit() {
const pageUrl = await this.fooService.getTabUrl();
const security = this.barService.getSecurity();
const controls: Array<ControlBase<any>> = [
new ControlTextbox({
key: "url",
order: 0,
readonly: true,
type: "text",
value: pageUrl
}),
new ControlDropdown({
key: "security",
label: "Security",
order: 2,
options: security,
type: "dropdown",
value: security[0].id
})
];
this.controls = controls;
}
I also attempted to integrate the async pipe in the view:
<form class="{{formClass}}" (ngSubmit)="onSubmit()" [formGroup]="form" role="form">
<app-form-control *ngFor="let ctrl of controls | async" [control]="ctrl | async" [form]="form | async"></app-form-control>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block" [disabled]="!form.valid">{{btnText}}</button>
</div>
</form>
Unfortunately, this approach did not resolve the issue.
Please refer to the provided screenshot for more details: https://i.sstatic.net/pUXbR.png
Below are additional snippets of code:
export class FormControlComponent implements OnInit {
@Input() public control: ControlBase<string | boolean | undefined>;
@Input() public form: FormGroup;
constructor() { }
get valid() {
return this.form.controls[this.control.key].valid;
}
get invalid() {
return !this.form.controls[this.control.key].valid && this.form.controls[this.control.key].touched;
}
ngOnInit() { }
}
export class DynamicFormComponent implements OnInit {
@Input() public controls: Array<ControlBase<any>> = [];
@Input() public btnText = "Submit";
@Output() public formSubmit: EventEmitter<any> = new EventEmitter<any>();
public form: FormGroup;
constructor(public _controlService: FormControlService) { }
ngOnInit() {
const sortedControls = this.controls.sort((a, b) => a.order - b.order);
this.form = this._controlService.toControlGroup(sortedControls);
}
onSubmit(): void {
this.formSubmit.emit(this.form.value);
}
}
export class FormControlService {
constructor() { }
public toControlGroup(controls: Array<ControlBase<any>>) {
const group: any = {};
controls.forEach(control => {
const validators: any = [];
// Required
if (control.required) {
validators.push(Validators.required);
}
// remove for brevity
group[control.key] = new FormControl(control.value || "", validators);
});
return new FormGroup(group);
}
}
I'm still relatively new to Angular and would appreciate any guidance on overcoming the issue related to initializing async data.