Daniel Heras
Daniel Heras

Reputation: 43

How to dynamically render any number of ng-template

Maybe the title of the post is not so accurate. What im making is a tabs component, where clicking on a tab the view changes. At the moment what i pass to the component is an array of tab identificators to render the tab buttons.

Now what i want is something like in the ng-content of the component pass it "n" number of templates or views like this:

<my-tabs-component [tabs]="['tab1','tab2']">
    <ng-template [view]="'tab1'">
        Tab 1 content
    <ng-template/>
    <ng-template [view]="'tab2'">
        Tab 2 content
    <ng-template/>
</my-tabs-component>

Obviously that syntax is probably incorrect, i read something about templateOutlet but i cant reach the correct aproach.

Answer me with a link to docs, the solution... Any help is welcome.

Upvotes: 2

Views: 731

Answers (1)

Andrei Gătej
Andrei Gătej

Reputation: 11924

As far as I can understand, you'll have to handle the tab switching logic(decide which tab is displayed) in the parent component. That is, the parent of my-tabs-component.

I think there are multiple ways to go about this, it depends on how complex your logic is.

These are the cases that come to mind:

Every tab is a component

If you have a small number of tabs(which I doubt), you can use ngComponentOutlet along with ng-container.

If you don't, you'll have to step into the low-level side of working with dynamic components. You can leverage the power of embedded views and host views.

An embedded view is created by an ng-template and it is called this way because it cannot display anything on its own, it must be attached to a view container.

A host view is a component that you will create imperatively. Still, you'll have to attach it to some other element, and the most convenient way is to use a view container.

You can read more about embedded views and host views here.

IMO, the must suitable candidate for a view container is an ng-container, which you can query for in your component class and gain access to the ViewContainerRef API.

I would encourage to try this approach because it offers you a way to preserve the tab states when switching between them, which means when you go from TabA to TAbB after adding some data to TabA and then going back to TabA, it could still have what you added before.

This can be achieved by using ViewContainerRef.detach() and ViewContainerRef.insert().

Roughly, this could be an implementation:

<my-tabs-component [tabs]="['tab1','tab2']">
 <ng-container #vcr></ng-container>
</my-tabs-component>
@Component({})
export class ParentComp {
 @ViewChild('vcr', { static: true, read: ViewContainerRef })
 private vcr: ViewContainerRef;
 private prevLoadedTabName = 'tab1';
 private compMap = new Map<string, ComponentRef<any>>();
 private tabsFactories = { /* ... You components here */ };

 loadTab (tabName) {
  this.compMap.set(this.prevLoadedTabName, this.vcr.detach());

  this.prevLoadedTabName = tabName;

  if (this.compMap.has(tabName)) {
    this.compMap.insert(this.compMap.get(tabName));

    return;
   }

  this.vcr.createComponent(this.tabsFactories[tabName]);
 }
}

Every tab is an ng-template

It is similar to the aforementioned case. Instead of ngComponentOutlet, you can use ngTemplateOutlet.

If you have multiple ng-templates, you can still use an ng-container as above, except that you would now want to use the ViewContainerRef.createEmbeddedView() along with ViewContainerRef.remove() / ViewContainerRef.clear().

In order to get the ng-templates from the view, you can use @ViewChildren(TemplateRef, { read: TemplateRef<any> }) templates: QueryList<TemplateRef<any>> and vcr.createEmbeddedView(templates.toArray()[idx]);

I might have missed some details, but I hope I could share my thoughts on this in a precise way.

Upvotes: 4

Related Questions