I recently faced an issue when trying to pass a template through several Angular components.
I was not able to pass the template declared in the parent and its context to the child component at last depth (here child-depth2).
<parent> // declare the template and its context
<child-depth1>
<child-depth2> // use the template
Trying different things (ng-content, template as input,...) I ended up with this code that seems to work in my case.
Is it the right way to do so ? Is there other way to pass a template and its context through several child components ?
ParentComponent
<child-depth1>
<ng-template #initialItemTemplate let-item>
<span>{{ item.label }}</span>
</ng-template>
</child-depth1>
ChildDepth1Component
@ContentChild("initialItemTemplate") readonly initialItemTemplate?: TemplateRef<unknown>;
<child-depth2>
<ng-template #itemTemplate let-item>
<ng-container *ngTemplateOutlet="initialItemTemplate; context: { $implicit: item }">
</ng-container>
</ng-template>
</child-depth2>
ChildDepth2Component
@ContentChild("itemTemplate") readonly itemTemplate?: TemplateRef<unknown>;
@for (item of items; track item.id) {
<ng-container *ngTemplateOutlet="itemTemplate; context: { $implicit: item }">
</ng-container>
}
You can use an @Input
to pass the template reference down to the component you need.
Complex objects are just memory references. you can also get rid of the passing down the chain, by using a service, but you might forget to cleanup the template references, so I am recommending this approach using @Input
instead.
...
export class Child1 {
@Input() itemTemplate!: TemplateRef<any>;
...
<child-depth1 [itemTemplate]="initialItemTemplate">
</child-depth1>
...
<ng-template #initialItemTemplate let-item>
<span>{{ item.label }}</span>
</ng-template>
import { Component, TemplateRef, Input } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
@Component({
selector: 'child-depth2',
standalone: true,
imports: [CommonModule],
template: `
@for (item of items; track item.id) {
<ng-container *ngTemplateOutlet="itemTemplate; context: { $implicit: item }">
</ng-container>
<br/>
}
`,
})
export class Child2 {
@Input() itemTemplate!: TemplateRef<any>;
name = 'Angular';
items = [
{ label: 'hello world 1', id: 1 },
{ label: 'hello world 2', id: 2 },
{ label: 'hello world 3', id: 3 },
];
}
@Component({
selector: 'child-depth1',
standalone: true,
imports: [Child2, CommonModule],
template: `
<child-depth2 [itemTemplate]="itemTemplate">
</child-depth2>
`,
})
export class Child1 {
@Input() itemTemplate!: TemplateRef<any>;
name = 'Angular';
}
@Component({
selector: 'app-root',
standalone: true,
imports: [Child1, Child2, CommonModule],
template: `
<child-depth1 [itemTemplate]="initialItemTemplate">
</child-depth1>
<br/>
<br/>
<br/>
<br/>
<ng-template #initialItemTemplate let-item>
<span>{{ item.label }}</span>
</ng-template>
rendered in parent
<br/>
<br/>
<ng-container *ngTemplateOutlet="initialItemTemplate; context: context">
</ng-container>
`,
})
export class App {
name = 'Angular';
item = { label: 'hello world' };
context = { $implicit: this.item };
}
bootstrapApplication(App);