How to make async data work with dynamic form pattern in angular

Viewed 2736

I'm using the dynamic form pattern in Angular with various form that we have. This is a convenient way for us as we only need to define our controls in ngOnInit and it will dynamically build the form that we need. However, there are some forms that values must be initialized and some of the values can be retrieve using async/await.

This is a problem with the dynamic form as when I'm initializing async data it throws errors on the console and the form is not showing up on the view.

I have tried to add async on the ngOnInit and await for the async data. As the sample code shows 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 add an async pipe on 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>

However, this is not working.

For more details please see the screenshot. enter image description here

Additional codes:

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 new and learning the Angular. Any suggestion on how I will overcome the issue when initializing async data?

1 Answers
Related