Reputation: 4944
NOTE- I realize this example could much more easily be done without using content projection. I am using it as a very simplified example.
Lets say I have the following component that lists of names in two different elements:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-name-list',
templateUrl: `
<div class='large'>
<h1>Large Names</h1>
<ng-content select="large"></ng-content>
</div>
<div class='small'>
<h1>Small Names</h1>
<ng-content select="small"></ng-content>
</div>
`,
styleUrls: ['./name-list.component.css']
})
export class TestNgContentComponent {
constructor() { }
}
I can then call this component from a template with two lists of names as follows:
<app-names-list>
<h1 ngProjectAs="large" *ngFor="let n of names">{{ n }}</h1>
<h2 ngProjectAs="small" *ngFor="let n of names">{{ n }}</h2>
</app-names-list>
Notice that the same data is used for both lists of names. Is it possible to replace the passed h1 and h2 tags, with a component that contains both, and projects them to the parent? E.g. something like this:
@Component({
selector: 'app-name',
template: `
<h1 ngProjectAs="large">{{name}}</h1>
<h2 ngProjectAs="small">{{name}}</h2>
`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
@Input() name: string;
}
and then modify the template to look like:
<app-names-list>
<app-name *ngFor="let n of names" [name]="n"></app-name>
</app-names-list>
Once I embed the ngProjectAs
directives in the app-name
template, this no longer works. Is there a way to do a projection like this from within a child component?
Upvotes: 2
Views: 4674
Reputation: 11934
Here is my approach:
I wrapped the header tags
in an ng-template
so it can be attached to an ng-container
as an embedded view.
You can read more about embedded
and host
views here.
@Component({
selector: 'app-name',
template: `
<ng-template #large>
<h1>{{name}}</h1>
</ng-template>
<ng-template #small>
<h2>{{name}}</h2>
</ng-template>
`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
@Input() name: string;
@ViewChild('large', { static: true, read: TemplateRef }) large: TemplateRef<any>;
@ViewChild('small', { static: true, read: TemplateRef }) small: TemplateRef<any>;
}
Here I have replaced ng-content
with ng-container
so that I can attach embedded views.
@Component({
selector: 'app-name-list',
template: `
<div class='large'>
<h1>Large Names</h1>
<ng-container #large></ng-container>
</div>
<ng-container #foo></ng-container>
<div class='small'>
<h1>Small Names</h1>
<ng-container #small></ng-container>
</div>
`,
})
export class TestNgContentComponent {
@ContentChildren(HelloComponent, { read: HelloComponent }) children: QueryList<HelloComponent>;
@ViewChild('large', { static: true, read: ViewContainerRef }) largeNamesContainer: ViewContainerRef;
@ViewChild('small', { static: true, read: ViewContainerRef }) smallNamesContainer: ViewContainerRef;
constructor() { }
ngAfterContentInit () {
this.populateView();
}
private populateView () {
this.largeNamesContainer.clear();
this.smallNamesContainer.clear();
this.children.forEach(child => {
this.largeNamesContainer.createEmbeddedView(child.large);
this.smallNamesContainer.createEmbeddedView(child.small);
});
}
}
Upvotes: 4