Angular Component Memory Leak - on Chrome and Microsoft Edge (could be all browsers, haven't tested all of them yet)
- I've tried the app below on the latest versions of Angular 11, 12, and 13, and I've been able to get them all to leak memory
- browser isn't garbage collecting Angular components when they get destroyed
- the components shallow size (from the memory inspector) seems small, but the retained size (from the memory inspector) is large
- see https://developer.chrome.com/docs/devtools/memory-problems/memory-101/ for terminology
- DOM Nodes are kept in memory, causing a significant memory leak
- I've been able to get this program to cause memory leaks in Chrome and Edge (haven't tried any others yet)
- it happens in both dev and production mode of angular
- it could have todo with the css property, display: flex
- I can't reproduce the leak on stackblitz. I can only get it to work on machines running the app locally
Sometimes it won't leak memory, sometimes it will. That's what makes this one of the biggest head scratchers I've faced in a while.
Please don't immediately write this off as "I can't replicate, so it must be fake", if when you first run the program below and it doesn't leak memory. Try it a couple times first, please. I swear it will eventually get into a state when started (because this bug has been plaguing me for a while), that causes it to leak memory.
I've narrow it down to this simple app, since this memory leak has been messing with a much larger production app that I've been working on.
Below is the simplified app that can be used to test with.
https://github.com/kevinpbaker/angular-memory-killer
package.json
{
"name": "memory-killer",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --port 4201",
"build": "ng build --prod --aot --buildOptimizer --commonChunk --vendorChunk --optimization --progress",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~11.2.14",
"@angular/cdk": "^11.2.13",
"@angular/common": "~11.2.14",
"@angular/compiler": "~11.2.14",
"@angular/core": "~11.2.14",
"@angular/forms": "~11.2.14",
"@angular/material": "^11.2.13",
"@angular/platform-browser": "~11.2.14",
"@angular/platform-browser-dynamic": "~11.2.14",
"@angular/router": "~11.2.14",
"rxjs": "~6.5.4",
"rxjs-compat": "^6.5.5",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1102.18",
"@angular/cli": "~11.2.18",
"@angular/compiler-cli": "~11.2.14",
"@angular/language-service": "~11.2.14",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~4.0.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.3.13",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.7.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "4.0.8"
}
}
app.module.ts
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppComponent, MemoryKiller2Component, MemoryKillerComponent} from './app.component';
@NgModule({
declarations: [
AppComponent,
MemoryKillerComponent,
MemoryKiller2Component
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Memory Killer</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
app.component.ts
import {Component} from '@angular/core';
import {timer} from 'rxjs';
import {map} from 'rxjs/operators';
@Component({
selector: 'app-root',
template: '<app-memory-killer *ngIf="killCycle$|async"></app-memory-killer>',
})
export class AppComponent {
// change 1000 to 1 if you want to speed up the memory killer
killCycle$ = timer(0, 1000).pipe(map(k => k % 2 !== 0));
}
@Component({
selector: 'app-memory-killer',
template: `
<div class="memory-killer">
<app-memory-killer2></app-memory-killer2>
<app-memory-killer2></app-memory-killer2>
<app-memory-killer2></app-memory-killer2>
</div>
`,
styles: [`.memory-killer { display: flex; }`]
})
export class MemoryKillerComponent { }
@Component({
selector: 'app-memory-killer2',
template: `
<div class="memory-killer2">
<div>Killing your memory</div>
<div>Killing your memory</div>
<div>Killing your memory</div>
</div>
`,
styles: [`.memory-killer2 { display: flex; }`],
})
export class MemoryKiller2Component { }
Microsoft Edge Detached Elements Viewer and Memory Inspector - Version 97.0.1072.55
Chrome Memory Inspector - Version 97.0.4692.99
Any input as to if this is an angular bug, a browser bug, or if you could point me to the right person that could help me figure this out would be greatly appreciated.
This issue was a browser bug. It has been fixed by the chromium dev team. See the last post within "Original Angular issue post"
Original Angular issue post: https://github.com/angular/angular/issues/45080
Chromium bug specifying fix: https://bugs.chromium.org/p/chromium/issues/detail?id=1308845