Primeng: Dynamic component loading for tab content

Created on 14 Apr 2016  路  12Comments  路  Source: primefaces/primeng

As mentioned at http://forum.primefaces.org/viewtopic.php?f=35&t=45097&p=141201&hilit=header#p141201.

Ideally, this could apply to creating dynamic dialogs/overlays as well.

Fair warning though: this is actually more complicated than it may initially sound, since it requires the use of DynamicComponentLoader, along with any directives needed by the content in advance. https://github.com/angular/angular/issues/7596

This comment may be of particular interest (this is basically how I've been doing any dynamic content loading in my Angular 2 apps): https://github.com/angular/angular/issues/7596#issuecomment-205982517

Most helpful comment

Can you explain further how you would envision this working with tab change events? I don't see how that's really an option.

It looks like relying on a change event would require hardcoded logic in the change handler method to detect which tab was added and what dynamic child component to insert. You also need some way to get a handle on a ViewContainerRef upon which createComponent() can be invoked.

Also, since there isn't a tab added event, it would require "one-time" detection logic to insert a dynamic child only the first time. I think it would also mean holding onto a reference to the new child so that it can be cleaned up/destroyed on tab close. Obviously, all of this would be better handled by the TabView/TabPanels themselves.

Something like this perhaps:

<p-tabView>
  <p-tabPanel *ngFor="let thisTab of tabPanels"
    [header]="thisTab.header" 
    [childComponent]="thisTab.childComponent">
  </p-tabPanel>
</p-tabView>

Now, it may be the case that you only want the Prime NG TabView to handle simple cases, since the work involved in supporting dynamic child components is clearly not trivial. But I'm not sure that using tab change events with the current TabView is really an option. Unless I'm missing something, I think developers would be forced to drop p-tabView and write their own tab component to really allow for this.

All 12 comments

Could it be handled on app side? We'd like to avoid complicating tabview.

Better handled on app side probably with tab change events, it is a bit our of scope of tabview component.

Can you explain further how you would envision this working with tab change events? I don't see how that's really an option.

It looks like relying on a change event would require hardcoded logic in the change handler method to detect which tab was added and what dynamic child component to insert. You also need some way to get a handle on a ViewContainerRef upon which createComponent() can be invoked.

Also, since there isn't a tab added event, it would require "one-time" detection logic to insert a dynamic child only the first time. I think it would also mean holding onto a reference to the new child so that it can be cleaned up/destroyed on tab close. Obviously, all of this would be better handled by the TabView/TabPanels themselves.

Something like this perhaps:

<p-tabView>
  <p-tabPanel *ngFor="let thisTab of tabPanels"
    [header]="thisTab.header" 
    [childComponent]="thisTab.childComponent">
  </p-tabPanel>
</p-tabView>

Now, it may be the case that you only want the Prime NG TabView to handle simple cases, since the work involved in supporting dynamic child components is clearly not trivial. But I'm not sure that using tab change events with the current TabView is really an option. Unless I'm missing something, I think developers would be forced to drop p-tabView and write their own tab component to really allow for this.

@brian428 , i agree with your solution. I have similar requirements and understand that there is no direct support in primeng tabs. How did you solve it in your case. I am looking for clues.

Unfortunately, I ended up having to hack together my own dynamic tab component, since there was no way to do this with the PrimeNG tabs.

This isn't complete, but hopefully it's enough to get others going down the right path:

The tabs array that I loop over in the view contains a reference to the desired child component to include:

export interface TabConfig {
  key?: string;
  title?: string;
  removable?: boolean;
  active?: boolean;
  childComponent?: Type<any>;
}

The tab component itself looks something like this:

import {
  Component, EventEmitter, HostBinding, Input, OnInit, OnDestroy, Output,
  ViewContainerRef, ComponentRef, Type, ViewChild, AfterContentInit
} from "@angular/core";

@Component({
  selector: "dynamic-tab",
  template: "<div #dynamic_content_container></div>"
})
...

@Input() public childComponent: Type<any> = null;
@ViewChild( "dynamic_content_container", { read: ViewContainerRef } ) dynamicContentContainer: ViewContainerRef;
protected dynamicComponent: ComponentRef<any> = null;

ngAfterContentInit(): void {
  if( this.childComponent !== null ) this.createChildComponent();
}

ngOnDestroy(): void {
  this.destroyChildComponent();
}

private createChildComponent() {
  this.destroyChildComponent();
  if( this.childComponent !== null ) {
    this.dynamicComponent = this.dynamicComponentService.createComponent(
      this.childComponent,
      this.dynamicContentContainer
    );
  }
  this.tabset.tabCreated.emit( this );
}

private destroyChildComponent() {
  if( this.dynamicComponent !== null ) this.dynamicComponent.destroy();
  this.dynamicComponent = null;
}

Finally, the DynamicComponentService looks like this:

@Injectable()
export class DynamicComponentService {

  constructor( private componentFactoryResolver: ComponentFactoryResolver ) {
  }

  createComponent<T>( component: Type<T>, parent: ViewContainerRef ): ComponentRef<T> {
    if( component === null || component === undefined ) {
      return null;
    }
    let componentFactory = this.componentFactoryResolver.resolveComponentFactory( component );
    let componentRef = parent.createComponent( componentFactory );
    return componentRef;
  }

}

IMO, this should be re-open. The ability to dynamically generate tabs in the tab view would be a really nice feature.

i am using one dynamic component for two tabs.Now i have two diff data sets that i want to use for two diff tabs..
But 2nd data set reflecting in both tabs.....
i want 1st data set has to bind 1st tab,and 2nd should bind on 2nd tab /...

Can't update view in Second tab.

And In ts
@ViewChild('test1Component', { read: ViewContainerRef }) appbEntry: ViewContainerRef;
@ViewChild('test2Component', { read: ViewContainerRef }) appnbEntry: ViewContainerRef;

createComponent1(){
this.appbEntry.clear();
const factory = this.resolver.resolveComponentFactory(Test1Component);
const componentRef = this.appbEntry.createComponent(factory);
}

createComponent2(){
this.appnbEntry.clear();
const factory = this.resolver.resolveComponentFactory(Test1Component);
const componentRef = this.appnbEntry.createComponent(factory);
}

HTML part














Is there a solution to this yet?

Yes. This is my fault
While creating 2nd component i used 1st component. That's why i am getting issue.

createComponent2(){
this.appnbEntry.clear();
const factory = this.resolver.resolveComponentFactory(Test2Component);
const componentRef = this.appnbEntry.createComponent(factory);
}

It works fine now.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

markgoho picture markgoho  路  3Comments

jisqaqov picture jisqaqov  路  3Comments

KannanMuruganmony picture KannanMuruganmony  路  3Comments

just-paja picture just-paja  路  3Comments

Helayxa picture Helayxa  路  3Comments