24

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 :)

filype
  • 8,034
  • 10
  • 40
  • 66
Varun Joshi
  • 241
  • 1
  • 2
  • 3
  • What Angular2 version are you using. As far as I remember this is an issue that was fixed recently. Not sure this fix is already included in RC.1 – Günter Zöchbauer Jun 05 '16 at 10:02
  • @GünterZöchbauer I'm using 2.0.0-rc.1, but doesn't work. – Varun Joshi Jun 05 '16 at 15:46
  • I suggest you try again after the next update. The last update is quite a while back and that fix wasn't too long ago, don't remember exactly but I'm quite sure it covers this problem. – Günter Zöchbauer Jun 05 '16 at 15:47
  • @GünterZöchbauer I can't find the precise link, but I found somewhere that ng-content is supposed to be run only once, thats how it's developed. So thats why I'm relying on ngIf and ngSwitch ( so ngContent runs once ) , but even those don't work. – Varun Joshi Jun 05 '16 at 18:38
  • There is better support planned for this requirement, looks like it's required for Material components as well. – Günter Zöchbauer Jun 12 '16 at 10:07
  • Any solution yet? I'm trying to figure out a way to accomplish the same thing. – Optiq Jan 03 '17 at 02:02
  • Why not make the component a simple class. Then you should be able to inherit from or extend it. This way it is decoupled from the template. – Mike Lunn Jan 21 '17 at 16:17

3 Answers3

4

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(); 
    }
}
Slawomir Dadas
  • 2,017
  • 2
  • 17
  • 19
1

In recent version above can be done using *ngIf="somevar", while you can pass "somevar" value using @input.

Example:

import { Component, OnInit, Input, Output, EventEmitter} from '@angular/core';
import {SearchPipe} from './../filters/search.pipe';

@Component({
  selector: 'itra-filter',
  templateUrl: 'filter.component.html',
  styleUrls: ['filter.component.scss'],
  inputs:['IsCheckboxEnabled','IsRadioboxEnabled'],
  outputs: ['itemClicked']
})
export class ItraFilterComponent {
 // Default Value
 public IsCheckboxEnabled:boolean = false;
 public IsRadioboxEnabled:boolean = false;

 constructor() {  
 }

 ngOnInit() {
  
 }
}
<span class="checkbox-control" *ngIf="IsCheckboxEnabled">
        <i class="icon icon_Check-box_filled"   *ngIf="y.checked"></i>
    <i class="icon icon_Check-box" *ngIf="!y.checked">        </i>
</span>
      
<span class="radiobox-control" *ngIf="IsRadioboxEnabled">
    <i class="icon icon_Radio-button-filled" *ngIf="y.checked"></i>
    <i class="icon icon_Radio-button" *ngIf="!y.checked"></i>
</span>
Sunil Garg
  • 14,608
  • 25
  • 132
  • 189
Sahil Gupta
  • 211
  • 3
  • 9
0

Perhaps, we can try the solution made here, angular 2 include html templates.

I am pretty much satisfied with this solution as I was in the same situation like switching Templates based on certain values and ultimately not to mess up the code with huge amount of lines.

I'm just describing the structure of my project a bit for clarification,

Component Structure
====================
Comp A
 -> Comp a1
 -> Comp a2 
 -> Comp a3 
 -> Comp a-consolidated*(declaring all needed component's selectors) 
Comp B 
Comp C 
Comp D 

This works with my problem and I recommend this :)