Search code examples
angularprimengtabviewangular-dynamic-components

PrimeNg TabView dynamic component


I am following this approach to dynamically create elements in an AngularApp based on a configuration JSON. Unfortunately that does not seem to work with all of the PrimeNG components. Especially not the TabView which I wanted to utilize as well.

I am not very familiar with Angular, but have been trying around the whole day. In my opinion the underlying issue is related to how the TabView recognizes the inherited TabPanels in the DOM, but unfortunately that isn't something I can change - can I?

Also, there seems to be a GitHub issue related to this, but it turns out that they ended up writing a custom tabs component. Does anyone see a way around this? It works quite well with other objects/elements from the Prime stack, just the tabview behaves a bit strange...


Solution

  • I was able to come around that issues. Basically it comes down to stopping the recursive foreach loop of the CreateDynamicComponentService in the TabView component, set the a tabs variable in the TabView and restart the recursive function from the service:

    create-dynamic-component.service

    createComponent(content: any, type: any, vcRef) {
        const componentRef = this.renderComp(content, type, vcRef)
        if (content.nodes && content.nodes.length) {
          if (!componentRef.instance.embeddedContainer) {
            const cmpName = componentRef.instance.constructor.name;
            if (cmpName != 'TabViewComponent') {
              throw new TypeError('Trying to render embedded content. ${cmpName} must have @ViewChild() embeddedContainer defined');
            }
          } else {
            content.nodes.forEach(type => {
              const typeP = this.contentMappings[type.type];
              this.createComponent(type, typeP, componentRef.instance.embeddedContainer);
            });
          }
        }
      }
    

    TabViewComponent

    @Component({
      selector: 'dynamic-page-tab-view',
      template: '<p-tabView dynamic="true" cache="false">
                    <p-tabPanel [header]="tab.header" *ngFor="let tab of tabs; let i = index" [selected]="i == 0">
                    {{ tab.header }}
                        <ng-container #panel></ng-container>
                    </p-tabPanel>
                </p-tabView>'
    })
    export class TabViewComponent implements AfterViewInit {
      @ViewChild('container', { read: ViewContainerRef,  static: true })
      embeddedContainer: ViewContainerRef;
    
      @ViewChildren('panel', {read: ViewContainerRef}) tabPanels: QueryList<ViewContainerRef>
    
      activeIndex: number;
      tabs: any = []
    
      constructor(private changeDetector : ChangeDetectorRef,
                  private viewContainerRef: ViewContainerRef,
                  private createDynamicComponentService: CreateDynamicComponentService,
                  @Inject(CONTENT_MAPPINGS) private contentMappings: any) { }
    
      contentOnCreate(values: { [key: string]: any; }): void {
        this.tabs = values["nodes"];
        this.activeIndex = 0;
      }
    
      ngAfterViewInit() {
        this.tabPanels.forEach((viewRef: ViewContainerRef, index: number) => {
          console.log(index, viewRef, this.tabs[index]);
          const type = this.tabs[index];
          const typeP = this.contentMappings[type.type];
          this.createDynamicComponentService.createComponent(type, typeP, viewRef);
          this.changeDetector.detectChanges();
        });
      }
    
    }
    

    it is important, that the TabPanel Component exists of a single container, only.

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

    The PrimeNg TabPanel itself is created and templated in the TabView component