angular: memory leak with angular animation

🐞 bug report


When using *ngIf to hide element which contains another element with angular animation trigger, appearing DOM nodes that garbage cleaner can`t remove. I inspect amount of DOM nodes by chrome performance monitor. In small app it`s doesn't matter but in app with a lot of components and animations it`s appear to be critical, especially on mobile

🔬 Minimal Reproduction

Create div which shown on *ngIf; inside it create another div with animation trigger like this: [@trigger]; try to hide and show parent div, while watching performance monitor; you notice that amount of DOM nodes increasing and this nodes can`t be cleaned by garbage collector

🌍 Your Environment

Angular Version:

Angular CLI: 7.3.9
Node: 10.15.3
OS: win32 x64
Angular: 7.2.15
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
@angular-devkit/architect         0.13.9
@angular-devkit/build-angular     0.13.9
@angular-devkit/build-optimizer   0.13.9
@angular-devkit/build-webpack     0.13.9
@angular-devkit/core              7.3.9
@angular-devkit/schematics        7.3.9
@angular/cli                      7.3.9
@ngtools/webpack                  7.3.9
@schematics/angular               7.3.9
@schematics/update                0.13.9
rxjs                              6.3.3
typescript                        3.2.4
webpack                           4.29.0

UPD: One of possible solutions: you can separate child element with animation trigger in new component, so ngDestroy of new component works fine and clear element when needed

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 15
  • Comments: 15 (3 by maintainers)

Most upvoted comments

This issue is driving me crazy for a couple of days now. I have analyzed this behavior a lot and I found out that it isn’t only a problem with *ngIf but also with *ngFor (where I faced this issue too) and I guess that it is also a problem with *ngSwitchCase (not tested). I think in general you can say: If you have a parent element which has child elements inside it with animation triggers and the parent element gets removed (e. g. by *ngFor or *ngIf) before the child elements get removed, the child elements with animation triggers attached are causing a DOM leak (in case of *ngFor with a huge list - a massive one).

I guess it doesn’t even matter if the parent element itself has an animation trigger attached to it.

For example (the scenario where I faced this issue):

I have a multidimensional array, where each entry of the array is another array. Like:

let arr = [
    ["test01", "test02", "test03"],
    ["test11", "test12", "test13"],
    ["test21", "test22", "test23"],
    ["test31", "test32", "test33"],

In my template I use a nested *ngFor to visualize this. Each array is a own div and inside that div each entry of the array is a paragraph. The paragraphs have animation triggers attached to them to animate them at application runtime.

For example:

<div *ngFor="let list of arr"">
    <p [@exampleAnimation]="exampleState *ngFor="let entry of list>{{entry}}</p>

Now, when you do arr.length = 0 to clear the array, all parapgraphs are leaked as Detached DOM elements. If you remove the animation trigger and do the same, that doesn’t happen.

This also doesn’t happen with an one dimensional array and a single *ngFor.

A workaround that I found out:

First clear all array entries inside the main array, then run change detection and let angular destroy all elements from the inner *ngFor (the paragraphs), then clear the main array and run change detection again so angular can now destroy all elements from the outer *ngFor (the divs). If you do it like that, everything gets cleaned up as expected.

But this workaround feels really hacky so I don’t think that it is a good option.

I also had a similar issue with *ngIf, where the parent container with the *ngIf attached had a child element with an animation trigger. This also caused a memory leak (of the whole component template). There I used the same workaround as above: First removed the child element itself with an *ngIf, then run change detection and let angular update the template, and then remove the parent container with the original *ngIf. This also fixed the issue.

But as I said, this workaround isn’t a solution because it is really dirty and feels hacky. So I hope this will get fixed soon, because it makes the @angular/animations package almost unusable for large applications with much animations.

also ran into this… especially annoying in our case because that loop with the animation is run through on a timer (it’s kind of a slideshow situation) which also is endless… which means this is guaranteed to crash the browser-tab given enough time 🧨

Still an issue in Angular 10 as far as I can see. A simple <*ngFor @withAnimation> causes un-GC’d detached DOM nodes. I don’t work for Google, but I’d question the P3 prioritization of any (easily reproducible) performance bug – just my 2 cents.

Closing based on Dario’s comment above. Please open a new issue (and include a repro) if the problem still exists in the most recent version of Angular. Thank you.

Could somebody take a look at this? @dario-piotrowicz maybe?

@JoostK I see. Can we please bump this up from P3? This issue is easily reproducible and will quickly destroy apps with any moderate-to-heavy use of animations.