angularangular-reactive-formsangular-dynamic-components

Angular Dynamic Component Creation not rendering dynamic child components


We've built a rather fancy dynamic component creation framework to build out complex forms based on json data. We have components that get created via

const questionComponent = FieldComponentMap[childField.shortName];    
viewContainerRef.createComponent<QuestionComponent>(questionComponent);

Those components may create child components of their own, etc, etc. It's all very elegant, and we like the design. We've got the whole thing "working", but it's not working well. Child, or perhaps grandchild components often don't render the first time around until something changes on the page. I'm sure we're just missing something simple, but we could use some more eyes to help us find what we're doing wrong.

We've tried moving all the dynamic component creation out of ngOnInit and into ngAfterContentInit, but that didn't help. We obviously want the complete form to render immediately.

Here's a StackBlitz demonstrating our issue (drastically simplified from our framework)... StackBlitz


Solution

  • I think you're lost in the hierarchy.

    To be more precisely, you're operating a wrong instance of FormGroup in

    array-question-wrapper.component.ts

    Try replacing

    this.createChildQuestionComponents(this.formGroup);
    

    with

    this.createChildQuestionComponents(this.formGroup.get(
         [this.formArrayName, this.formArrayIndex]) as FormGroup);
    

    Forked Stackblitz

    Another option

    When working with dynamically creating forms you don't need to wrap all your ng-templates with formGroup, formArrayName directives. They are not tied to your child dynamically created controls anyway.

    So:

    parent-array-question.component.ts

    Replace

    componentRef.instance.containingControl = this.formGroup;
    

    with

    componentRef.instance.containingControl = arrayItem;
    

    array-question-wrapper.component.ts

    template should be:

    <div class="child-questions">
      <ng-template appQuestionHost></ng-template>
    </div>
    

    or just <ng-template appQuestionHost></ng-template> if you don't need any additional classes here or you can use host element to style it.

    Forked Stackblitz 2