Dynamic template based on value, not variable with ngTemplateOutlet

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

Using angular 4.1 I thought that a template for ngTemplateOutlet would be the best way to do this, the idea is that I can set all checkboxes to be the same, and all radio objects are 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 created this plunker as a proof of concept but it doesn't work. All the code is in the file src/app.ts

.

I want 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 the variable to indicate which template to use?

+3


source to share


2 answers


As I said in the comment, you must pass a property TemplateRef

for ngTemplateOutlet

. This can be done as follows:

@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);
  }
}

      

Plunger example

+4


source


I would change the approach, here's my 2 cents:

Create a component for each typology of options (checkbox, radio, selection, etc.).

Store them in a constant, mapping the component name as a string to the component class, for example:

export const optionTypes = {
    'TypeRadio': TypeRadio,
    'TypeCheckBox': TypeCheckBox,
};

      

In component:

 private optionsModule: NgModuleFactory<any>; // we have our components for our options declared in OptionsModule, for example
 private optionTypes = optionTypes;

 constructor(private compiler: Compiler) {

        // Declaring Options Module
        this.optionsModule = compiler.compileModuleSync(OptionsModule);
 }

      

In component template:

<fieldset *ngFor="let question of questions">
                <ng-container *ngComponentOutlet="optionTypes[question.type];
                                    ngModuleFactory: optionsModule;"></ng-container>
            </fieldset>

      



Note that the attributes must be changed in your object data for this type

:

questions = [
      { question: "my checkbox question", type: "TypeCheckBox", values: ["checkbox1","checkbox2","checkbox3","checkbox4"] },
      { question: "my radiobutton question", type: "TypeRadio", values: ["radio1","radio2","radio3","radio4"] }
    ];

      

Summarizing:

  • We create OptionModule
  • We create a component (with its template and logic) for each parameter / question type
  • We add the name of these components to the attribute of type

    our data object. (or create a simple matching method, which: radio

    โ†’ TypeRadio

    )
  • We are using NgComponentOutlet to dynamically render our components.
  • We are using NgModuleFactory to render these components from the imported module.

Result:

We have a dynamic component loading system for our quiz. Each component has logic and offers you tremendous possibilities for adding interesting functionality and behavior!

An example of this approach (I used this to have 100% dynamic field forms: inputs, selections, radio buttons, checkboxes, etc.): Angular2: use Pipe to render templates dynamically

+2


source







All Articles