1

I'm going through the Angular tutorial and can see the usefulness of having a component with more than one template. e.g. here's a component with a 'full' and 'min' template

import { Component, Input} from '@angular/core'
import { Hero } from './hero'

@Component({
    selector: 'my-hero-detail',
    template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
    `
})
@Component({
    selector: 'my-hero-detail-min',
    template: `
    <div *ngIf="hero">
      </label>{{hero.id}}</label>
    </div>
    `
})
export class HeroDetailComponent {
    @Input()
    hero: Hero;    
}

Needless to say, it doesn't work: how can I make it work?

Kim Kern
  • 54,283
  • 17
  • 197
  • 195
Steve Dunn
  • 21,044
  • 11
  • 62
  • 87
  • 4
    You can't, it would have to be two components, or use e.g. `*ngSwitch` within a single template to toggle two views. – jonrsharpe Dec 03 '16 at 11:38
  • 2
    The questions says more than one template , where as you've also changed the selector , which simply means another component altogether , because even when u're using this component , you'll need to change the selectors. If you only want to change the template , this is your answer : http://stackoverflow.com/questions/31692416/dynamic-template-urls-in-angular-2 – Milad Dec 03 '16 at 11:54

2 Answers2

2

You can't do that this way. However there are several ways to achieve a similar behavior.

First one is just implementing the logic in a separate class which will be inherited by facade classes. Despite the people having doubts about this method it works. See plunkr

import { Component, Input} from '@angular/core';
import { Hero } from './hero';

class HeroDetailAbstractComponent {
    @Input()
    hero: Hero;    
}

@Component({
    selector: 'my-hero-detail',
    template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
    `
})
export class HeroDetailComponent extends HeroDetailAbstractComponent {};

@Component({
    selector: 'my-hero-detail-min',
    template: `
    <div *ngIf="hero">
      </label>{{hero.id}}</label>
    </div>
    `
})
export class HeroDetailMinComponent extends HeroDetailAbstractComponent {}

Another approach is to use another input parameter let's say called mode:

import { Component, Input} from '@angular/core'
import { Hero } from './hero'

@Component({
    selector: 'my-hero-detail',
    template: `
    <div *ngIf="hero && mode === 'max'">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
    <div *ngIf="hero && mode === 'min'">
      </label>{{hero.id}}</label>
    </div>
    `
})
export class HeroDetailComponent {
    @Input()
    hero: Hero;    

    @Input()
    mode: string; 
}

Both methods simply work. I personally prefer the first way because it looks easier to write separate unit tests and it feels more natural.

smnbbrv
  • 23,502
  • 9
  • 78
  • 109
  • 1
    The first example would not work if you have decorators , like @Input , in the parent class , you'd need to define those inside the child component as well , which means defining them multiple times . The only way to handle that issue is to define your own decorator. – Milad Dec 03 '16 at 11:58
  • @xe4me that's true. However, the `Input` is not the case when it's coming to the router driven components. Also redeclaring the input is not that complicated process and it could be a good trade for reducing the template complexity – smnbbrv Dec 03 '16 at 12:08
  • hmm , not sure , because component easily grow bigger , and Input is not the only one , this would go further to ViewChild , Output and ... . I've been there , this would cause heaps of issue as you starting making it more complicated. – Milad Dec 03 '16 at 12:13
  • By the way , what exactly do you mean by router driven components ? and how would that be related to Input properties/ ? – Milad Dec 03 '16 at 12:14
  • @xe4me agree, for complex components it's a problem. By router driven I mean the components which are loaded by the router, thus they don't have input properties – smnbbrv Dec 03 '16 at 16:07
  • @xe4me The first example **WORKS** with `Input` and `ViewChild` decorators (and I am sure with many more decorators as well). Please don't confuse people like that anymore. See the updated answer – smnbbrv Dec 04 '16 at 07:58
  • 1
    @smnbbrv you're right, it wasn't working before , looks like it's fixed. good. – Milad Dec 04 '16 at 21:57
1

If you want different selectors, it would have to be two components. Alternatively, you could use the ngSwitch directive do something like:

@Component({
    selector: 'my-hero-detail',
    template: `
    <div *ngIf="hero" *ngSwitch="full">
      <div ngSwitchCase="true">
        <h2>{{hero.name}} details!</h2>
        <div><label>id: </label>{{hero.id}}</div>
        <div>
          <label>name: </label>
          <input [(ngModel)]="hero.name" placeholder="name"/>
        </div>
      </div>
      <div ngSwitchDefault>
        <label>{{hero.id}}</label>
      </div>
    </div>
    `
})
export class HeroDetailComponent {
    @Input()
    hero: Hero;    

    @Input()
    full: boolean;
}

Then include the component as:

<my-hero-detail [hero]="hero" [full]="true"></my-hero-detail>
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437