I'm trying to create a form builder using angular 2. An very basic example is as follows:
this.fields = [{name: 'Name', type: 'text'}, {name: 'Age', type: 'number'}];
But I also want to support custom elements like:
this.fields = [
{name: 'Name', type: text},
{name: 'Age', type: 'custom', customid: 'Ctl1'},
{name: 'Whatever', type: 'custom', customid: 'Ctl2'}
];
// template:
<super-form [fields]="fields">
<Ctl1><input type="number" ...><Ctl1>
<Ctl2><whaterver-control ...><Ctl2>
</super-form>
In my form builder component I then have something like:
<div *ngFor="let f of fields">
<div [ngSwitch]="f.type">
<span *ngSwitchWhen="'custom'">
<ng-content select="f.customid"></ng-content>
</span>
</div>
</div>
But given that I'm here this obviously does not work. Is this an ng2 limitation? If so, I guess I could hard code say 5 optional content elements and check if they are specified and not have dynamic selects but this is a hack.
Cheers
Since you are binding to a variable, please use the one way binding syntax like:
<ng-content [select]="f.customid"></ng-content>
Correction
ng-content
is for static projection only. It's meant to be fast "transcluding". Please check this issue for more info
I know this is an old question, but this is one of the first places I landed when searching for this functionality so I'll add how I was able to solve it.
ngContent is only for static projection, so you can't use it to do any bindings. If you need bindings in your projected content you can use ngTemplateOutlet and ngOutletContext.
Usage Example:
<my-component>
<template let-item="item">
<h1>{{item?.label}}</h1> - <span>{{item?.id}}</span>
</template>
</my-component>
Inside MyComponent you can access that template using ContentChild:
@ContentChild(TemplateRef) templateVariable: TemplateRef<any>;
Then inside your component's template you pass that to ngTemplateOutlet like this:
<div *ngFor="let item of list">
<template [ngTemplateOutlet]="templateVariable" [ngOutletContext]="{item: item}"></template>
</div>
The ngOutletContext is optional but it allows you to create the object that you will be binding to in the template. Notice that I created a property item
in the context object. That matches the name I put on the template here: let-item="item"
Now the consumer of my-component
can pass in the template to be used for each item in the list.
Credit:
This answer led me in the right direction.
You can do this already if you wrap the content with a <template>
element.
// renders the template
// `item` is just an example how to bind properties of the host component to the content passed as template
@Directive({
selector: '[templateWrapper]'
})
export class TemplateWrapper implements OnChanges {
private embeddedViewRef:EmbeddedViewRef<any>;
@Input()
private item:any;
constructor(private viewContainer:ViewContainerRef) {
console.log('TemplateWrapper');
}
@Input() templateWrapper:TemplateRef<any>;
ngOnChanges(changes:{[key:string]:SimpleChange}) {
if (changes['templateWrapper']) {
if (this.embeddedViewRef) {
this.embeddedViewRef.destroy();
}
console.log('changes', changes);
this.embeddedViewRef = this.viewContainer.createEmbeddedView(this.templateWrapper, {item: this.item});
}
if (this.embeddedViewRef) {
console.log('context', this.embeddedViewRef.context);
this.embeddedViewRef.context.item = this.item;
}
}
}
// just some component that is used in the passed template
@Component({
selector: 'test-component',
styles: [':host { display: block; border: solid 2px red;}'],
directives: [TemplateWrapper],
template: `
<div>test-comp</div>
<div>prop: {{prop | json}}</div>
`
})
export class TestComponent {
@Input() prop;
constructor() {
console.log('TestComponent');
}
}
// the component the `<template>` is passed to to render it
@Component({
selector: 'some-comp',
directives: [TemplateWrapper],
template: `
<div>some-comp</div>
<div *ngFor="let f of fields">
<div [ngSwitch]="f.type">
<span *ngSwitchCase="'custom'">
<template [templateWrapper]="template" [item]="f" ></template>
</span>
</div>
</div>
`
})
export class SomeComponent {
constructor() {
console.log('SomeComponent');
}
@ContentChild(TemplateRef) template;
fields = [
{name: 'a', type: 'custom'},
{name: 'b', type: 'other'},
{name: 'c', type: 'custom'}];
}
// the component where the `<template>` is passed to another component
@Component({
selector: 'my-app',
directives: [SomeComponent, TestComponent],
template: `
<some-comp>
<template let-item="item">
<div>some content</div>
<div>item: {{item | json}}</div>
<test-component [prop]="item"></test-component>
</template>
</some-comp>
`,
})
export class App {
constructor() {
console.log('AppComponent');
}
}
Plunker example