Search code examples
javascriptangulartypescriptangular-dynamic-components

Creating nested dynamic components in angular


I Want to know how to create nested dynamic components and maintains its parent child relationship.

For example, I have data like this,

- A
--A.1
--A.2
-B
--B.1
-C 

I wanted to create the component like this,

<A>
   <A1></A1>
   <A2></A2>
</A>
<B>
   <B1></B1>
</B>
<C></C>

But with my code I could only create parent component or child component. But not both.

Below is my code,

  setRootViewContainerRef(view: ViewContainerRef): void {
    this.rootViewContainer = view;
  }

  createComponent(content: any, type: any) {
 console.log(content);
    if (content.child && content.child.length > 0) {
      content.child.forEach(type => {
        const typeP = this.contentMappings[type.type];
        this.createComponent(type, typeP);
      });
    } else {
      this.renderComp(content,type)
    }
  }

  renderComp(content,type) {
    if (!type) {
      return
    }
    this.componentFactory = this.componentFactoryResolver.resolveComponentFactory(type);
    this.componentReference = this.rootViewContainer.createComponent(this.componentFactory);

    if (this.componentReference.instance.contentOnCreate) {
      this.componentReference.instance.contentOnCreate(content);
    }
  }

With this code, I get this output.

Link to working example, StackBlitz

Please help me to resolve this issue.


Updated.

Even after adding the viewChild, It still throws the viewchild not defined.

Refer this image, In the component.instance I'm not seeing the view child element.

enter image description here

Updated stackblitz link https://stackblitz.com/edit/angular-dynamic-new-mepwch?file=src/app/content/a/a.component.ts


Solution

  • You should create ViewContainer on each level that is going to render child components:

    a.component.html

    <p>
    a works!
    </p>
    <ng-container #container></ng-container>
    

    a.component.ts

    export class AComponent implements OnInit {
      @ViewChild('container', { read: ViewContainerRef, static: true }) embeddedContainer: ViewContainerRef;
    

    And then render component to dedicated container:

    create-dynamic-component.service.ts

    @Injectable()
    export class CreateDynamicComponentService {
      constructor(
        private componentFactoryResolver: ComponentFactoryResolver,
        @Inject(CONTENT_MAPPINGS) private contentMappings: any,
        private inlineService: InlineService
      ) { }
    
    
      createComponent(content: any, type: any, vcRef) {
        const componentRef = this.renderComp(content, type, vcRef)
        if (content.child && content.child.length) {
          if (!componentRef.instance.embeddedContainer) {
            const cmpName = componentRef.instance.constructor.name;
            throw new TypeError(`Trying to render embedded content. ${cmpName} must have @ViewChild() embeddedContainer defined`);
          }
    
           content.child.forEach(type => {
            const typeP = this.contentMappings[type.type];
            this.createComponent(type, typeP, componentRef.instance.embeddedContainer);
          });
        }
      }
    
      renderComp(content,type, vcRef: ViewContainerRef) {
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(type);
        const componentRef = vcRef.createComponent<any>(componentFactory);
    
        if (componentRef.instance.contentOnCreate) {
          componentRef.instance.contentOnCreate(content);
        }
    
        return componentRef;
      }
    }
    

    Note how renderComp method takes ViewContainerRef from the component with children:

     this.createComponent(type, typeP, componentRef.instance.embeddedContainer);
    

    Forked Stackblitz