swimlane / ngx-datatable

✨ A feature-rich yet lightweight data-table crafted for Angular
http://swimlane.github.io/ngx-datatable/
MIT License
4.63k stars 1.68k forks source link

Memory leak issue (seems to be related to ResizeableDirective and Renderer2.createElement) #1554

Open ruslansadygov opened 6 years ago

ruslansadygov commented 6 years ago

I'm submitting a ... (check one with "x")

[ x ] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, post on Stackoverflow or Gitter

Current behavior There appears to be a permanent memory leak after 11.1.6 version. It can be reproduced in Angular 5 application, when datatable component is used inside views for different routes. Switching between routes back and forth makes overall number of DOM nodes to grow in Chrome browser. There is no memory leak issue with 11.1.5 version. In addition, there are lot of DataTableComponent instances and its children if take a memory snapshot.

Expected behavior There should be no memory leak.

Reproduction of the problem

  1. Install 11.1.5 version of library and switch between routes with datatable component for route views back and forth - no memory leak.

  2. Do the same but with 11.1.6 - there is a memory leak - DOM nodes grow and consumed memory as well. Memory snapshot outputs equal number of datatable instances to navigation between routes.

After more detailed investigation, I found out that memory leak disappears for 11.1.6 version in case if disable resizing for column:

<ngx-datatable-column [resizeable]="false">

After looking into source code of ResizeableDirective I can see that Renderer2 was used:

ngAfterViewInit(): void {
    const renderer2 = this.renderer;
    if (this.resizeEnabled) {
      const node = renderer2.createElement('span');
      renderer2.addClass(node, 'resize-handle');
      renderer2.appendChild(this.element, node);
    }
  }

New element is created, but it's not removed from renderer on component destroy - please see https://stackoverflow.com/questions/44465653/what-is-the-correct-way-to-destroy-an-element-created-with-renderer2

Finally, I tried other version of library and found out that fix with disabling column resize doesn't work starting from 11.2.0 version and memory leak is present again. Looking into source code of ResizeableDirective I see next:

ngAfterViewInit(): void {
  const renderer2 = this.renderer;
  const node = renderer2.createElement('span');
  if (this.resizeEnabled) {
    renderer2.addClass(node, 'resize-handle');
  } else {
    renderer2.addClass(node, 'resize-handle--not-resizable');
  }
  renderer2.appendChild(this.element, node);
}

Element is created with Renderer2 doesn't matter on condition and that's why workaround doesn't help.

I recommend to keep reference on "node" and remove it inside "ngOnDestroy". Also inspect code for same case with Renderer2 usage and apply same changes there as well.

What is the motivation / use case for changing the behavior? Continue using library and have no memory leaks.

Please tell us about your environment: MacOS, WebStroms, ng

rsadyhov commented 6 years ago

Any updates on this ticket? It appears to block migration to Angular 6 for us, because 13.1.0 version has the same issue with memory leak.

redsend commented 5 years ago

Same issue using version 11.3.2 and I can't upgrade it now... is there any workaround do clean the memory?

Toktik commented 5 years ago

This fixes the memory leak: https://github.com/swimlane/ngx-datatable/pull/1679/files

gilsdav commented 4 years ago

Hello, Using "observable-profiler" I can detect a lot of observables never completed/unsubscribed. The "memory leak" that comes a lot is on "OrderableDirective":

Error
    at EventEmitter.subscribe (index.js:44)
    at EventEmitter.subscribe (core.js:35410)
    at subscribe (swimlane-ngx-datatable.js:657)
    at DefaultKeyValueDiffer.forEachAddedItem (core.js:27635)
    at OrderableDirective.updateSubscriptions (swimlane-ngx-datatable.js:672)
    at OrderableDirective.ngAfterContentInit (swimlane-ngx-datatable.js:623)
    at callProviderLifecycles (core.js:32321)
    at callElementProvidersLifecycles (core.js:32293)
    at callLifecycleHooksChildrenFirst (core.js:32275)
    at checkAndUpdateView (core.js:44276)

I have +/- 100 subscriptions added to the stack each time I display the table. The most "funny" is that I had set [reorderable]= "false" on each column. Why do I have memory leak on OrderableDirective ? ^^