Search code examples
angulartypescriptangular-dynamic-components

Creating dynamic nested components from json response


I'm trying to created nested dynamic components based on json response.

public content={type:'paragraph',depth:1,text:'Root',entityRanges:[{type:'LINK',offset:83,length:16,data:{target:'_self',url:'/index.htm'}}],embbeded:[{type:'text',text:'This is Text component'}]}

So in the above structure, it as type paragraph so the ParagraphComponent need to render first.

It as an array object embbeded, in this array I wanted to render TextComponent.

So the final output should be like this,

<paraComp><p><textComp>This is Text component</textComp></p></paraComp>

Below is what I tried,

Main Component

export class AppComponent implements OnInit {
  @ViewChild('container', { read: ViewContainerRef })
  public target: ViewContainerRef;
  public content = { type: 'paragraph', depth: 1, text: 'Root', entityRanges: [{ type: 'LINK', offset: 83, length: 16, data: { target: '_self', url: '/index.htm' } }], embbeded: [{ type: 'text', text: 'This is Text component' }] }

  constructor(private createDynamicComponentService: CreateDynamicComponentService) { }
  ngOnInit() {
    this.createDynamicComponentService.createComponent(this.content, this.target);
  }
}

Main HTML

<ng-container #container></ng-container>

createDynamicComponentService

export class CreateDynamicComponentService {
  private rootViewContainer: ViewContainerRef;
  private componentFactory: ComponentFactory<any>;
  private componentReference;
  constructor(
    private componentFactoryResolver: ComponentFactoryResolver,
    @Inject(CONTENT_MAPPINGS) private contentMappings: any
  ) { }

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

  createComponent(content: any, container: ViewContainerRef) {
    const type = this.contentMappings[content.type];
    console.log(type);
    this.componentFactory = this.componentFactoryResolver.resolveComponentFactory(type);
    this.componentReference = this.rootViewContainer.createComponent(this.componentFactory);
    this.componentReference.instance.contentOnCreate(content);
  }
}

Link to Project https://stackblitz.com/edit/angular-dynamic-new?file=src/app/app.component.ts

I took reference from this https://github.com/sparkles-dev/ng-content-driven-angular/blob/master/src/app/reactive-content/content-host/content-host.component.ts

Based on it only I've created my project. But not able to make it work.

Please help.


Solution

  • The simplest way of doing this might look like:

    @Injectable()
    export class CreateDynamicComponentService {
      constructor(
        private componentFactoryResolver: ComponentFactoryResolver,
        @Inject(CONTENT_MAPPINGS) private contentMappings: any
      ) { }
    
      createComponent(content: any, container: ViewContainerRef) {
        const type = this.contentMappings[content.type];
    
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(type);
        const componentRef = container.createComponent<any>(componentFactory);
    
        if (componentRef.instance.contentOnCreate) {
          componentRef.instance.contentOnCreate(content);
        }
    
        if (!content.embedded) return;
    
        // render children recursively
        for (const embeddedContent of content.embedded) {
          // in order to render children component must define ViewContainerRef
          if (!componentRef.instance.embeddedContainer) {
            const cmpName = componentRef.instance.constructor.name;
            throw new TypeError(`Trying to render embedded content. 
              ${cmpName} must have @ViewChild() embeddedContainer defined`);
          }
    
          this.createComponent(embeddedContent, componentRef.instance.embeddedContainer);
        }
      }
    
    }
    

    In order to render child components component should define container where the children will be rendered, for example:

    paragraph.component.html

    <p>
      <ng-container #embeddedContainer></ng-container>
    </p>
    

    paragraph.component.ts

    export class ParagraphComponent implements OnInit {
      @ViewChild('embeddedContainer', { read: ViewContainerRef }) embeddedContainer: ViewContainerRef;
    

    Forked Stackblitz