Dynamic template based on value rather than variable with ngTemplateOutlet

crthompson picture crthompson · May 19, 2017 · Viewed 12.2k times · Source

I'm trying to mock up a dynamic set of questions. Think of a quiz, where one question is multiple choice, the second is single answer, the third is yes no..etc.

Using angular 4.1, I thought that templating with ngTemplateOutlet would be the best way to go for this, the idea being that I can style all the checkboxes to be the same, and all the radiobuttons the same etc.

@Component({
  selector: 'my-component',
  template: `
    <div *ngFor="let item of items">
        <ng-template [ngTemplateOutlet]="item.type" [ngOutletContext]="{ item: item }"></ng-template>
    </div>`
})
export class MyComponent {
  @Input() items: any[];
}

@Component({
  selector: 'my-app',
  template: `
    <div>
      <my-component [items]="questions">
        <ng-template #question let-item="item">{{item?.question}}</ng-template>
        <ng-template #check let-item="item">checkboxes here {{item?.type}} - {{item?.values}}</ng-template>
        <ng-template #radio let-item="item">radio buttons here{{item?.type}} - {{item?.values}}</ng-template>
        <ng-template #bool let-item="item">boolean here{{item?.type}} - {{item?.values}}</ng-template>
        <ng-template #textbox let-item="item">textbox here{{item?.type}} - {{item?.values}}</ng-template>
      </my-component>
    </div>`
})
export class App {
  @ViewChild('question') question;
  @ViewChild('type') type;
  @ViewChild('values') values;
  questions = [
      { question: "my checkbox question", type: "check", values: ["checkbox1","checkbox2","checkbox3","checkbox4"] },
      { question: "my radiobutton question", type: "radio", values: ["radio1","radio2","radio3","radio4"] } ,
      { question: "my boolean question", type: "bool", values: ["yes", "no"] } ,
      { question: "my textbox question", type: "textbox", values: ["maybe something maybe nothing"] } 
    ];

I've created this plunker as a proof of concept effort, but it is not working. All the code is in the src/app.ts file.

What I want is something like this:

My checkbox question?
checkbox 1, checkbox2, checkbox3

my radio button question
radiobutton1, radiobutton2, radiobutton3

my boolean question?
yes, no 

How can I modify this code to use the value of variable to indicate which template to use?

Answer

yurzui picture yurzui · May 19, 2017

As i said in comment you have to pass TemplateRef for ngTemplateOutlet property. It could be done like this:

@Directive({
  selector: 'ng-template[type]'
})
export class QuestionTemplate {
  @Input() type: string;
  constructor(public template: TemplateRef) {}
}

app.html

<my-component [items]="questions">
  <ng-template type="question" ...>...</ng-template>
  <ng-template type="check" ...>...</ng-template>
  ...

my.component.ts

@Component({
  selector: 'my-component',
  template: `
    <div *ngFor="let item of items">
        <ng-template 
           [ngTemplateOutlet]="dict['question']" 
           [ngOutletContext]="{ item: item }"></ng-template>
        <ng-template 
           [ngTemplateOutlet]="dict[item.type]" 
           [ngOutletContext]="{ item: item }"></ng-template>
    </div>`
})
export class MyComponent {
  @Input() items: any[];

  @ContentChildren(QuestionTemplate) templates: QueryList<QuestionTemplate>;

  dict = {};

  ngAfterContentInit() {
    this.templates.forEach(x => this.dict[x.type] = x.template);
  }
}

Plunker Example