I can create a component dynamically and project some template inside it just fine with the following code -
@Component({
selector: 'dynamic',
template: `
<p>Dynamic Component</p>
<ng-content></ng-content>
`
})
export class DynamicComponent { }
@Component({
selector: 'tester',
template: `
<p>Tester Component</p>
<ng-container #cnt></ng-container>
<ng-template #tpl>
<p>[Projected Content]</p>
</ng-template>
`
})
export class TesterComponent implements AfterViewInit {
@ViewChild('cnt', { read: ViewContainerRef }) cnt: ViewContainerRef
@ViewChild('tpl') tpl: TemplateRef<any>
ngAfterViewInit(): void {
let content = [this.tpl.createEmbeddedView(null).rootNodes];
this.cnt.createComponent(DynamicComponent, { projectableNodes: content });
}
}
But I want to project another component (e.g. the following one) inside the dynamic component -
@Component({
selector: 'content',
template: `<p>Content Component</p>`
})
export class ContentComponent { }
so that the resulting output becomes the equivalent of -
<p>Tester Component</p>
<dynamic>
<content></content>
</dynamic>
Cannot figure out how to achieve that. Any help/suggestion would be appreciated. Thanks.
We can use the createComponent
API to dynamically create the component and then using contentRef.location.nativeElement
project the content onto the dynamic created component, note the double array syntax, that got this working!
test.ts
import {
Component,
ViewChild,
TemplateRef,
ViewContainerRef,
ContentChild,
createComponent,
Injector,
inject,
ElementRef,
EnvironmentInjector,
} from '@angular/core';
@Component({
selector: 'dynamic',
standalone: true,
template: `
<p>Dynamic Component</p>
<ng-content></ng-content>
`,
})
export class DynamicComponent {}
@Component({
selector: 'tester',
standalone: true,
template: `
<p>Tester Component</p>
<ng-container #cnt></ng-container>
<ng-template #tpl>
<p>[Projected Content]</p>
</ng-template>
`,
})
export class TesterComponent {
@ViewChild('cnt', { read: ViewContainerRef }) cnt!: ViewContainerRef;
@ViewChild('tpl') tpl!: TemplateRef<any>;
@ContentChild('content') contentChild!: TemplateRef<any>;
constructor(
private injector: EnvironmentInjector,
private elementRef: ElementRef
) {}
ngAfterViewInit(): void {
const elementInjector = Injector.create({
providers: [
{
provide: 'MyToken',
useValue: 'Token',
},
],
});
const contentRef = createComponent(ContentComponent, {
environmentInjector: this.injector,
elementInjector,
});
this.cnt.createComponent(DynamicComponent, {
projectableNodes: [[contentRef.location.nativeElement]] as any,
});
}
}
@Component({
selector: 'app-content',
standalone: true,
template: `<p>Content Component</p>`,
})
export class ContentComponent {}
We can just access the content using ContentChild
and then using a or condition, we can either use the content child, or use the view child, whichever is present in the defined order!
ts
import {
Component,
ViewChild,
TemplateRef,
ViewContainerRef,
ContentChild,
} from '@angular/core';
@Component({
selector: 'dynamic',
standalone: true,
template: `
<p>Dynamic Component</p>
<ng-content></ng-content>
`,
})
export class DynamicComponent {}
@Component({
selector: 'tester',
standalone: true,
template: `
<p>Tester Component</p>
<ng-container #cnt></ng-container>
<ng-template #tpl>
<p>[Projected Content]</p>
</ng-template>
`,
})
export class TesterComponent {
@ViewChild('cnt', { read: ViewContainerRef }) cnt!: ViewContainerRef;
@ViewChild('tpl') tpl!: TemplateRef<any>;
@ContentChild('content') contentChild!: TemplateRef<any>;
ngAfterViewInit(): void {
console.log('content', this.tpl);
console.log(this.contentChild);
this.cnt.createComponent(DynamicComponent, {
projectableNodes: [
this.contentChild?.createEmbeddedView(null)?.rootNodes ||
this.tpl?.createEmbeddedView(null)?.rootNodes,
],
});
}
}
@Component({
selector: 'app-content',
standalone: true,
template: `<p>Content Component</p>`,
})
export class ContentComponent {}
main.ts
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { ContentComponent, DynamicComponent, TesterComponent } from './test';
@Component({
selector: 'app-root',
standalone: true,
imports: [DynamicComponent, TesterComponent, ContentComponent],
template: `
<tester/>
<tester>
<ng-template #content>
<app-content/>
</ng-template>
</tester>
`,
})
export class App {
name = 'Angular';
}
bootstrapApplication(App);