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?
ngTemplateOutlet is a powerful tool for creating customisable components. It is used by many Angular libraries to enable users to provide custom templates.
TemplateReflinkRepresents an embedded template that can be used to instantiate embedded views. To instantiate embedded views based on a template, use the ViewContainerRef method createEmbeddedView() .
ng-container serves as a container for elements which can also accept structural directives but is not rendered to the DOM, while ng-template allows you to create template content that is not rendered until you specifically (conditionally or directly) add it to the DOM.
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
I would change the approach, here my 2 cents:
Create a component for each typology of options (checkbox, radio, select, etc...).
Store them in an constant, mapping the name of the component as string with the component class, such as:
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's template:
<fieldset *ngFor="let question of questions">
<ng-container *ngComponentOutlet="optionTypes[question.type];
ngModuleFactory: optionsModule;"></ng-container>
</fieldset>
Note that for this to work, your object data should have the type
attributes changed:
questions = [
{ question: "my checkbox question", type: "TypeCheckBox", values: ["checkbox1","checkbox2","checkbox3","checkbox4"] },
{ question: "my radiobutton question", type: "TypeRadio", values: ["radio1","radio2","radio3","radio4"] }
];
Summing up:
type
attribute of our data Object. (or create a simple mapping method that: radio
-> TypeRadio
)Result:
We have a dynamic component loading system for our quizz. Each component has it's logic and offers you huge possibilities for adding cool features and behaviors!
An example of this approach (I used this to have 100% dynamic formfields: inputs, select, radio buttons, checkboxes, etc.): Angular2: Use Pipe to render templates dynamically
@Component({
selector: 'my-component',
template: `
<div *ngFor="let item of items">
<ng-container [ngTemplateOutlet]="item.type" [ngTemplateOutletContext]="{ item: item }">
</ng-container>
</div>
<ng-template #question let-item="item"></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>
`
})
export class MyComponent {
@Input() items: any[];
}
You can call your template's based on your "item.type", Rest of the structure looks good in your approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With