Reputation: 1001
In our code base we have a virtual repeat directive that uses Renderer2 to create a div like this:
this.renderer2.createElement('div');
In the ngOnDestroy method we are destroying it like this:
this.renderer.destroyNode(this.offsetBeforeEl);
This worked fine and we had no issues until we built the application in prod mode and we started getting the following error:
main.js?d73b003…:formatted:87193 Uncaught (in promise) TypeError: this.renderer.destroyNode is not a function
I added a breakpoint to the that line and found that in fact destroyNode is not a method on Renderer2. I went to the source code for angular and found the comment above the method in the abstract class defintion:
/**
* This property is allowed to be null / undefined,
* in which case the view engine won't call it.
* This is used as a performance optimization for production mode.
*/
destroyNode: ((node: any) => void)|null;
So I checked out the code for the view and saw this:
if (view.renderer.destroyNode) {
destroyViewNodes(view);
}
if (isComponentView(view)) {
view.renderer.destroy();
}
If I can't rely on this method existing, what is the correct way to destroy a node that was dynamically created using Renderer2?
Upvotes: 17
Views: 26015
Reputation: 145890
To remove all children you can must iterate over the child elements.
Note that children
isn't a true array so it must be converted first.
Array.from(this.elementRef.nativeElement.children).forEach(child => {
console.log('children.length=' + this.elementRef.nativeElement.children.length);
this.renderer.removeChild(this.elementRef.nativeElement, child);
});
The logging statement demonstrates that the children aren't actually removed in 'real time' from the DOM - which is why a while loop doesn't work to remove lastChild
.
Use caution if these elements weren't originally added by you - because if they're components you may get memory leaks or all kinds of weirdness. The above was only tested for elements I'd added myself to an empty element node.
I thought I should be able to do [...this.elementRef.nativeElement.children]
, but this complained that slice
doesn't exist - so I just reverted to Array.from
.
Upvotes: 9
Reputation: 105439
Use renderer.removeChild
method.
until we built the application in prod mode
destroyNode
method is defined on the DebugRenderer2 which is not used in the production mode.
The correct way to handle node removal can be inferred from the way angular does it, for example this execRenderNodeAction function:
function execRenderNodeAction(
view: ViewData, renderNode: any, action: RenderNodeAction, parentNode: any, nextSibling: any,
target?: any[]) {
const renderer = view.renderer;
switch (action) {
case RenderNodeAction.RemoveChild:
renderer.removeChild(parentNode, renderNode);
break;
So, use removeChild
like this:
const parent = this.renderer.createElement('div');
const child = this.renderer.createElement('span');
this.renderer.appendChild(parent, child);
// using remove child
this.renderer.removeChild(parent, child);
Upvotes: 13