One Component Multiple Templates based on Condition

Varun Joshi picture Varun Joshi · Jun 4, 2016 · Viewed 21.4k times · Source

So here's the deal. I have a component thats very well written and being used in a lot of places. Now I need to use the same component, but want a different template to be rendered, based upon a condition.

I tried a lot.

1) Tried using multiple component decorators - no luck

2) Tried multiple level of abstractions, where I just ended up creating more components - bad idea

3) Can literally copy the whole component, and just change the selector and template - bad idea

4) Currently I was trying this:

<div *ngIf="!isWizard">
    <ul class="nav" role="tablist">
        <ng-content select="tab-link"></ng-content>
    </ul>
    <ng-content select="tab-content"></ng-content>
</div>


<div *ngIf="isWizard">
    <nav class="nav-panel sidenav">
        <ng-content select=".wizard-title"></ng-content>
            <ul class="nav" role="tablist">
                <ng-content select="tab-link"></ng-content>
            </ul>

    </nav>

    <main class="settings-panel content-area">
        <ng-content select="tab-content"></ng-content>
    </main>

</div>

I set the isWizard property as true/false. Now the problem is, ng-content runs only once. So when isWizard is true, even though the div block is displayed, ng-content doesn't run ( cause it ran in the above block ).

5) Instead of using ngIf I also tried ngSwitch - didn't work

I'm desperate now. Please help :)

Answer

Slawomir Dadas picture Slawomir Dadas · Mar 12, 2017

As far as I know it cannot be done using ng-content but you could achieve this using templates (or ng-templates in Angular 4+). So instead of passing content directly to your component, just wrap it in <template> like that:

<my-component [isWizard]="true">
    <template>Hello World!</template>
</my-component>

Then you need to inject the template to your component with @ContentChild(TemplateRef) and render it as many times as you wish.

@Component({
  selector: "my-component",
  template: `
    <div *ngIf="!isWizard">
      first: <template [ngTemplateRenderer]="template"></template>
    </div>
    <div *ngIf="isWizard">
      second: <template [ngTemplateRenderer]="template"></template>
    </div>`
})
export class MyComponent {

  @ContentChild(TemplateRef)
  private template: TemplateRef<any>;

  @Input("isWizard")
  private isWizard: boolean;
}

There is one last thing, our component uses ngTemplateRenderer which is a simple utility directive that renders templates passed by reference. Here's the code for that directive:

@Directive({ selector: '[ngTemplateRenderer]'})
export class TemplateRenderer implements OnInit, OnDestroy {

    @Input("ngTemplateRenderer")
    private template: TemplateRef<any>;

    private view: EmbeddedViewRef<any>;

    constructor(private container: ViewContainerRef) {}

    ngOnInit(): void {
      this.view = this.container.createEmbeddedView(this.template);
    }

    ngOnDestroy(): void {
      this.view.destroy(); 
    }
}