Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the correct way to destroy an element created with Renderer2?

Tags:

angular

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?

like image 765
instantaphex Avatar asked Jun 09 '17 19:06

instantaphex


People also ask

How do I use Renderer2?

Using Renderer2 Use ElementRef & ViewChild to get the reference to the DOM element, which you want to manipulate. @ViewChild('hello', { static: false }) divHello: ElementRef; Use the methods like setProperty , setStyle etc to change the property, styles of the element as shown below.

Is Renderer2 deprecated?

The Renderer class has been marked as deprecated since Angular version 4. This section provides guidance on migrating from this deprecated API to the newer Renderer2 API and what it means for your app.

Why would you use Renderer methods instead of using native element methods?

Using the Renderer for manipulating the DOM doesn't break server-side rendering or Web Workers (where direct access to the DOM would break). ElementRef is a class that can hold a reference to a DOM element. This is again an abstraction to not break in environments where the browsers DOM isn't actually available.

What does Renderer do in Angular?

The Renderer2 class is an abstraction provided by Angular in the form of a service that allows to manipulate elements of your app without having to touch the DOM directly.


2 Answers

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.

like image 91
Simon_Weaver Avatar answered Nov 08 '22 13:11

Simon_Weaver


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);
like image 20
Max Koretskyi Avatar answered Nov 08 '22 12:11

Max Koretskyi