I have a few DIVs that may be marked with a directive special
. That directive has certain logic that works. Now, I'd like to add a component within the DIV that is equiped with the directive.
<div special ...>Monkey</div>
<div regular ...>Donkey</div>
Following the docs on Using ViewContainerRef (suggested by this answer to a similar issue), I run the following in my directive.
@Directive({ selector: "[special]" })
export class SpecialDirective {
private viewRef = inject(ViewContainerRef); ...
constructor() {
const icon = this.viewRef.createComponent(IconComponent);
}
It works just as described in the docs: the created icon (corresponding to LeafContent
) is placed in the DOM right after the DIV that's marked as special (corresponding to InnerItem
). And this is precisely my problem: I want the icon to be a part of the inner HTML of the DIV that's governed by the directive.
I can't move the directive inwards for various reasons related to its logic. I've tried to access viewRef.element
and inspected other fields and methods of it. To no avail. Setting index on the creation didn't help. The closest I got was detaching/inserting from the docs but it only let me manage the order of elements after my DIV, not within it.
I've seen this answer, but it's not going to help in my case because the alteration is happening after the user clicks on the DIV way past it's been created and placed in the view.
How can I alter the inside of the viewRef
instance? Optimally, I'd like to remove some tags inside it as well besides amending the icon.
Since you want to insert the component without any child elements created, we can use createComponent
standalone method, which takes the input of environment injector and we can use appendChild
of Renderer2
to append this created element to the original elementRef
.
export class InnerItem {
constructor(
private viewContainer: ViewContainerRef,
private injector: Injector,
private element: ElementRef,
private envInjector: EnvironmentInjector,
private renderer: Renderer2
) {}
loadContent() {
const componentRef = createComponent(LeafContent, {
environmentInjector: this.envInjector,
});
this.renderer.appendChild(
this.element.nativeElement,
componentRef.location.nativeElement
);
componentRef.changeDetectorRef.detectChanges();
}
}
import {
Component,
createComponent,
ViewContainerRef,
Injector,
ElementRef,
EnvironmentInjector,
Renderer2,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
@Component({
selector: 'leaf-content',
template: `
<div>This is the leaf content</div>
`,
})
export class LeafContent {}
@Component({
selector: 'inner-item',
template: `
<button (click)="loadContent()">Load content</button>
`,
})
export class InnerItem {
constructor(
private viewContainer: ViewContainerRef,
private injector: Injector,
private element: ElementRef,
private envInjector: EnvironmentInjector,
private renderer: Renderer2
) {}
loadContent() {
const componentRef = createComponent(LeafContent, {
environmentInjector: this.envInjector,
});
this.renderer.appendChild(
this.element.nativeElement,
componentRef.location.nativeElement
);
componentRef.changeDetectorRef.detectChanges();
}
}
@Component({
selector: 'outer-container',
imports: [InnerItem],
template: `
<p>This is the start of the outer container</p>
<inner-item />
<p>This is the end of the outer container</p>
`,
})
export class OuterContainer {}
@Component({
selector: 'app-root',
imports: [OuterContainer],
template: `
<outer-container/>
`,
})
export class App {
name = 'Angular';
}
bootstrapApplication(App);
You can create a template reference variable
#insertHere
to explicitely specify where to insert the new component, we can use viewChild
and specify the read
property to get the ViewContainerRef
which has the createComponent
method.
@Component({
selector: 'inner-item',
template: `
<button (click)="loadContent()">Load content</button>
<ng-container #insertHere></ng-container>
`,
})
export class InnerItem {
insertHere = viewChild('insertHere', {
read: ViewContainerRef,
});
loadContent() {
this.insertHere()?.createComponent(LeafContent);
}
}
import { Component, viewChild, ViewContainerRef } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
@Component({
selector: 'leaf-content',
template: `
This is the leaf content
`,
})
export class LeafContent {}
@Component({
selector: 'inner-item',
template: `
<button (click)="loadContent()">Load content</button>
<ng-container #insertHere></ng-container>
`,
})
export class InnerItem {
insertHere = viewChild('insertHere', {
read: ViewContainerRef,
});
loadContent() {
this.insertHere()?.createComponent(LeafContent);
}
}
@Component({
selector: 'outer-container',
imports: [InnerItem],
template: `
<p>This is the start of the outer container</p>
<inner-item />
<p>This is the end of the outer container</p>
`,
})
export class OuterContainer {}
@Component({
selector: 'app-root',
imports: [OuterContainer],
template: `
<outer-container/>
`,
})
export class App {
name = 'Angular';
}
bootstrapApplication(App);