I've created a simple Angular app with one Form and a Table (PrimeNg DataTable).
Just to get a substantial difference in memory utilization with initial and final readings, I performed multiple Form Post calls (300 times) and navigated Table till page 20 with 1000 rows per page also sorting table 5 times with all columns and took the Heap snapshots with initial and final states (with all add-ons disabled in private/incognito tabs and with angular-cli app running in dev env).
In chrome, the heap memory size increased from 55 MB to 146 MB (91 MB gain)

In Chrome, the heap memory size increased from 23.16 MB to 137.1 MB (113.95 MB gain)

I am unsubscribing from all the subscriptions when my component destroys (this will have no effect as this is a single component) also I've set changeDetectionStrategy to onPush.
app.component.html:
<div [ngBusy]="{busy: busy, message: 'Loading data, this may take few minutes!'}"></div>
<div style="width:50%">
<form [formGroup]="taskForm" (ngSubmit)="addTask(taskForm.value)">
<div class="width:100%;float:left; clear:both;">
<div style="width:20%;float:left">
<input type="text" [formControl]="index" class="form-control" placeholder="index" />
</div>
<div style="width:20%;float:left">
<input type="text" [formControl]="name" class="form-control" placeholder="name" />
</div>
<div style="width:20%;float:left">
<input type="text" [formControl]="userId" class="form-control" placeholder="userId" />
</div>
<div style="width:20%;float:left">
<input type="text" [formControl]="mobile" class="form-control" placeholder="mobile" />
</div>
<div style="width:20%;float:left">
<input type="date" [formControl]="taskDate" class="form-control" placeholder="taskDate" />
</div>
</div>
<div class="width:100%;float:left; clear:both;">
<button type="submit" class="btn btn-success">Add Task</button>{{taskPostCount}}
</div>
</form>
<code *ngIf="addTaskResponse">{{addTaskResponse | json}}</code>
</div>
<div style="text-align:center">
<p-dataTable [dataKey]="'_id'" *ngIf="isTableVisible()" [value]="table" expandableRows="true" (onFilter)="onColumnFilterChanged($event)"
(onSort)="onTableColumnSortChanged($event)" [lazy]="true">
<p-column field="index" header="Index" [filter]="true" [sortable]="true"></p-column>
<p-column field="name" header="Task Name" [filter]="true" [sortable]="true"></p-column>
<p-column field="mobile" header="Mobile" [filter]="true" [sortable]="true"></p-column>
<p-column field="taskDate" header="Task Date"></p-column>
<p-column field="createdAt" header="Date Created"></p-column>
<p-column field="updatedAt" header="Date Updated"></p-column>
</p-dataTable>
<p-paginator [rowsPerPageOptions]="[10,20,30,50,100,200,300,400,500,1000]" [first]="tableSearchConfig.firstRowIndex" [totalRecords]="tableSearchConfig.totalRecords"
*ngIf="isTableVisible()" [rows]="tableSearchConfig.rowsPerPage" (onPageChange)="onPageChanged($event)"></p-paginator>
</div>
app.component.ts:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [AppService, TimeFromNow],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
// Properties declarations
private unsubscribe: Subject<boolean> = new Subject<boolean>();
constructor(private appService: AppService, private timeFromNow: TimeFromNow, private ref: ChangeDetectorRef, fb: FormBuilder) {
this.taskForm = fb.group({
'index': ['', Validators.compose([])],
'name': ['', Validators.compose([])],
'userId': ['', Validators.compose([])],
'mobile': ['', Validators.compose([])],
'taskDate': ['', Validators.compose([])],
});
this.index = this.taskForm.controls['index'];
this.name = this.taskForm.controls['name'];
this.userId = this.taskForm.controls['userId'];
this.mobile = this.taskForm.controls['mobile'];
this.taskDate = this.taskForm.controls['taskDate'];
this.setTableSearchConfig();
}
ngOnInit() {
this.addBulkTasksOnLoad();
}
ngOnDestroy() {
this.unsubscribe.next(true);
this.unsubscribe.complete();
this.unsubscribe.unsubscribe();
}
addBulkTasksOnLoad() {
this.busy = this.appService.addTaskOnLoad().subscribe((res: any) => {
this.loadTable();
}, (err: any) => {
});
}
addTask(taskForm: any) {
this.taskPostCount++;
this.appService.addTask(taskForm).takeUntil(this.unsubscribe).subscribe((res: any) => {
this.addTaskResponse = res;
},
err => {
this.addTaskResponse = err;
});
}
loadTable(paginateEvent?: PaginateEvent, sortEvent?: SortEvent, filterEvent?: FilterEvent) {
this.appService.getTable(this.tableSearchConfig).takeUntil(this.unsubscribe).subscribe((res: any) => {
for (const history of res.data) {
history.updatedAt = this.timeFromNow.transform(history.updatedAt);
}
this.table = res.data;
this.setTableSearchConfig(paginateEvent, sortEvent, filterEvent, this.tableSearchConfig.pageNumberToRequest, res.totalRecords);
this.ref.detectChanges();
});
}
.
.
.
.
}
Is it a case of memory leak and if yes what exactly I am doing wrong or it is normal behavior to have this increase in memory after heavy usage of the app? The app's frame rate also dropped at the end quite significantly.