Skip to content

Commit 43854ce

Browse files
committed
WEB-428: Fix memory leaks in multiple components by implementing proper subscription cleanup
- Fixed memory leaks in NotificationsTrayComponent by adding takeUntil to all subscriptions - Fixed memory leaks in SharesAccountChargesStepComponent with proper unsubscribe pattern - Fixed memory leaks in RecurringDepositProductChargesStepComponent with takeUntil operator - Fixed memory leaks in FixedDepositProductChargesStepComponent with subscription cleanup - Fixed memory leaks in ShareProductChargesStepComponent with proper OnDestroy implementation - Fixed memory leaks in LoanProductChargesStepComponent by unsubscribing from valueChanges All components now implement OnDestroy interface and use Subject + takeUntil pattern to properly clean up subscriptions and prevent memory leaks.
1 parent f43695e commit 43854ce

File tree

8 files changed

+124
-47
lines changed

8 files changed

+124
-47
lines changed

src/app/organization/holidays/create-holiday/create-holiday.component.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export class CreateHolidayComponent implements OnInit, OnDestroy {
115115
private _database: ChecklistDatabase,
116116
private createHoliday: CreateHoliday
117117
) {
118-
this.route.data.subscribe((data: { offices: any; holidayTemplate: any }) => {
118+
this.route.data.pipe(takeUntil(this.destroy$)).subscribe((data: { offices: any; holidayTemplate: any }) => {
119119
this.officesData = data.offices;
120120
this.repaymentSchedulingTypes = data.holidayTemplate;
121121
// Constructs trie everytime data changes
@@ -128,7 +128,7 @@ export class CreateHolidayComponent implements OnInit, OnDestroy {
128128
this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
129129

130130
// Listens for changes in CheckListDatabase
131-
this._database.dataChange.subscribe((data) => {
131+
this._database.dataChange.pipe(takeUntil(this.destroy$)).subscribe((data) => {
132132
this.dataSource.data = data;
133133
});
134134
}
@@ -357,15 +357,18 @@ export class CreateHolidayComponent implements OnInit, OnDestroy {
357357
locale,
358358
offices
359359
};
360-
this.organizationService.createHoliday(data).subscribe((response: any) => {
361-
this.router.navigate(
362-
[
363-
'../',
364-
response.resourceId
365-
],
366-
{ relativeTo: this.route }
367-
);
368-
});
360+
this.organizationService
361+
.createHoliday(data)
362+
.pipe(takeUntil(this.destroy$))
363+
.subscribe((response: any) => {
364+
this.router.navigate(
365+
[
366+
'../',
367+
response.resourceId
368+
],
369+
{ relativeTo: this.route }
370+
);
371+
});
369372
}
370373

371374
/**

src/app/organization/holidays/edit-holiday/edit-holiday.component.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class EditHolidayComponent implements OnInit, OnDestroy {
6060
private settingsService: SettingsService,
6161
private router: Router
6262
) {
63-
this.route.data.subscribe((data: { holiday: any; holidayTemplate: any }) => {
63+
this.route.data.pipe(takeUntil(this.destroy$)).subscribe((data: { holiday: any; holidayTemplate: any }) => {
6464
this.holidayData = data.holiday;
6565
this.holidayData.repaymentSchedulingTypes = data.holidayTemplate;
6666
this.reSchedulingType = this.holidayData.reschedulingType;
@@ -165,10 +165,13 @@ export class EditHolidayComponent implements OnInit, OnDestroy {
165165
dateFormat,
166166
locale
167167
};
168-
this.organizatioService.updateHoliday(this.holidayData.id, data).subscribe((response) => {
169-
/** TODO Add Redirects to ViewMakerCheckerTask page. */
170-
this.router.navigate(['../'], { relativeTo: this.route });
171-
});
168+
this.organizatioService
169+
.updateHoliday(this.holidayData.id, data)
170+
.pipe(takeUntil(this.destroy$))
171+
.subscribe((response) => {
172+
/** TODO Add Redirects to ViewMakerCheckerTask page. */
173+
this.router.navigate(['../'], { relativeTo: this.route });
174+
});
172175
}
173176

174177
/**

src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { Component, Input, OnInit } from '@angular/core';
1+
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
22
import { UntypedFormControl } from '@angular/forms';
33
import { MatDialog } from '@angular/material/dialog';
44

5+
/** RxJS Imports */
6+
import { Subject } from 'rxjs';
7+
import { takeUntil } from 'rxjs/operators';
8+
59
import { TranslateService } from '@ngx-translate/core';
610
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
711
import { MatButton, MatIconButton } from '@angular/material/button';
@@ -47,7 +51,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
4751
FormatNumberPipe
4852
]
4953
})
50-
export class FixedDepositProductChargesStepComponent implements OnInit {
54+
export class FixedDepositProductChargesStepComponent implements OnInit, OnDestroy {
5155
@Input() fixedDepositProductsTemplate: any;
5256
@Input() currencyCode: UntypedFormControl;
5357

@@ -61,6 +65,8 @@ export class FixedDepositProductChargesStepComponent implements OnInit {
6165
'chargeTimeType',
6266
'action'
6367
];
68+
/** Subject to handle subscription cleanup */
69+
private destroy$ = new Subject<void>();
6470

6571
constructor(
6672
public dialog: MatDialog,
@@ -74,7 +80,12 @@ export class FixedDepositProductChargesStepComponent implements OnInit {
7480
} else {
7581
this.chargesDataSource = [];
7682
}
77-
this.currencyCode.valueChanges.subscribe(() => (this.chargesDataSource = []));
83+
this.currencyCode.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => (this.chargesDataSource = []));
84+
}
85+
86+
ngOnDestroy() {
87+
this.destroy$.next();
88+
this.destroy$.complete();
7889
}
7990

8091
addCharge(charge: any) {

src/app/products/loan-products/loan-product-stepper/loan-product-charges-step/loan-product-charges-step.component.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { Component, Input, OnInit } from '@angular/core';
1+
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
22
import { UntypedFormControl } from '@angular/forms';
33
import { MatDialog } from '@angular/material/dialog';
44

5+
/** RxJS Imports */
6+
import { Subject } from 'rxjs';
7+
import { takeUntil } from 'rxjs/operators';
8+
59
import { TranslateService } from '@ngx-translate/core';
610
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
711
import { MatButton, MatIconButton } from '@angular/material/button';
@@ -51,7 +55,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
5155
FormatNumberPipe
5256
]
5357
})
54-
export class LoanProductChargesStepComponent implements OnInit {
58+
export class LoanProductChargesStepComponent implements OnInit, OnDestroy {
5559
@Input() loanProductsTemplate: any;
5660
@Input() currencyCode: UntypedFormControl;
5761
@Input() multiDisburseLoan: UntypedFormControl;
@@ -69,6 +73,8 @@ export class LoanProductChargesStepComponent implements OnInit {
6973
];
7074

7175
pristine = true;
76+
/** Subject to handle subscription cleanup */
77+
private destroy$ = new Subject<void>();
7278

7379
constructor(
7480
public dialog: MatDialog,
@@ -86,8 +92,13 @@ export class LoanProductChargesStepComponent implements OnInit {
8692
this.chargesDataSource = this.loanProductsTemplate.charges || [];
8793
this.pristine = true;
8894

89-
this.currencyCode.valueChanges.subscribe(() => (this.chargesDataSource = []));
90-
this.multiDisburseLoan.valueChanges.subscribe(() => (this.chargesDataSource = []));
95+
this.currencyCode.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => (this.chargesDataSource = []));
96+
this.multiDisburseLoan.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => (this.chargesDataSource = []));
97+
}
98+
99+
ngOnDestroy() {
100+
this.destroy$.next();
101+
this.destroy$.complete();
91102
}
92103

93104
addCharge(charge: any) {

src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-charges-step/recurring-deposit-product-charges-step.component.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { Component, Input, OnInit } from '@angular/core';
1+
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
22
import { UntypedFormControl } from '@angular/forms';
33
import { MatDialog } from '@angular/material/dialog';
44

5+
/** RxJS Imports */
6+
import { Subject } from 'rxjs';
7+
import { takeUntil } from 'rxjs/operators';
8+
59
import { TranslateService } from '@ngx-translate/core';
610
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
711
import { MatButton, MatIconButton } from '@angular/material/button';
@@ -45,7 +49,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
4549
ChargesFilterPipe
4650
]
4751
})
48-
export class RecurringDepositProductChargesStepComponent implements OnInit {
52+
export class RecurringDepositProductChargesStepComponent implements OnInit, OnDestroy {
4953
@Input() recurringDepositProductsTemplate: any;
5054
@Input() currencyCode: UntypedFormControl;
5155

@@ -59,6 +63,8 @@ export class RecurringDepositProductChargesStepComponent implements OnInit {
5963
'chargeTimeType',
6064
'action'
6165
];
66+
/** Subject to handle subscription cleanup */
67+
private destroy$ = new Subject<void>();
6268

6369
constructor(
6470
public dialog: MatDialog,
@@ -72,7 +78,12 @@ export class RecurringDepositProductChargesStepComponent implements OnInit {
7278
} else {
7379
this.chargesDataSource = [];
7480
}
75-
this.currencyCode.valueChanges.subscribe(() => (this.chargesDataSource = []));
81+
this.currencyCode.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => (this.chargesDataSource = []));
82+
}
83+
84+
ngOnDestroy() {
85+
this.destroy$.next();
86+
this.destroy$.complete();
7687
}
7788

7889
addCharge(charge: any) {

src/app/products/share-products/share-product-stepper/share-product-charges-step/share-product-charges-step.component.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { Component, Input, OnInit } from '@angular/core';
1+
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
22
import { UntypedFormControl } from '@angular/forms';
33
import { MatDialog } from '@angular/material/dialog';
44

5+
/** RxJS Imports */
6+
import { Subject } from 'rxjs';
7+
import { takeUntil } from 'rxjs/operators';
8+
59
import { TranslateService } from '@ngx-translate/core';
610
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
711
import { MatButton, MatIconButton } from '@angular/material/button';
@@ -47,7 +51,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
4751
FormatNumberPipe
4852
]
4953
})
50-
export class ShareProductChargesStepComponent implements OnInit {
54+
export class ShareProductChargesStepComponent implements OnInit, OnDestroy {
5155
@Input() shareProductsTemplate: any;
5256
@Input() currencyCode: UntypedFormControl;
5357

@@ -63,6 +67,8 @@ export class ShareProductChargesStepComponent implements OnInit {
6367
];
6468

6569
pristine = true;
70+
/** Subject to handle subscription cleanup */
71+
private destroy$ = new Subject<void>();
6672

6773
constructor(
6874
public dialog: MatDialog,
@@ -75,7 +81,12 @@ export class ShareProductChargesStepComponent implements OnInit {
7581
this.chargesDataSource = this.shareProductsTemplate.charges || [];
7682
this.pristine = true;
7783

78-
this.currencyCode.valueChanges.subscribe(() => (this.chargesDataSource = []));
84+
this.currencyCode.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => (this.chargesDataSource = []));
85+
}
86+
87+
ngOnDestroy() {
88+
this.destroy$.next();
89+
this.destroy$.complete();
7990
}
8091

8192
addCharge(charge: any) {

src/app/shared/notifications-tray/notifications-tray.component.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
33

44
/** RxJS Imports */
5-
import { forkJoin } from 'rxjs';
5+
import { forkJoin, Subject } from 'rxjs';
6+
import { takeUntil } from 'rxjs/operators';
67

78
/** Custom Services */
89
import { NotificationsService } from 'app/notifications/notifications.service';
@@ -46,6 +47,8 @@ export class NotificationsTrayComponent implements OnInit, OnDestroy {
4647
unreadNotifications: any[] = [];
4748
/** Timer to refetch notifications every 60 seconds */
4849
timer: any;
50+
/** Subject to handle subscription cleanup */
51+
private destroy$ = new Subject<void>();
4952

5053
/**
5154
* Gets router link prefix from notification's objectType attribute
@@ -70,11 +73,13 @@ export class NotificationsTrayComponent implements OnInit, OnDestroy {
7073
constructor(public notificationsService: NotificationsService) {
7174
forkJoin([
7275
this.notificationsService.getNotifications(true, 9),
73-
this.notificationsService.getNotifications(false, 9)]).subscribe((response: any[]) => {
74-
this.readNotifications = response[0].pageItems;
75-
this.unreadNotifications = response[1].pageItems;
76-
this.setNotifications();
77-
});
76+
this.notificationsService.getNotifications(false, 9)])
77+
.pipe(takeUntil(this.destroy$))
78+
.subscribe((response: any[]) => {
79+
this.readNotifications = response[0].pageItems;
80+
this.unreadNotifications = response[1].pageItems;
81+
this.setNotifications();
82+
});
7883
}
7984

8085
ngOnInit() {
@@ -83,6 +88,8 @@ export class NotificationsTrayComponent implements OnInit, OnDestroy {
8388

8489
ngOnDestroy() {
8590
this.destroy();
91+
this.destroy$.next();
92+
this.destroy$.complete();
8693
}
8794

8895
public destroy() {
@@ -101,10 +108,13 @@ export class NotificationsTrayComponent implements OnInit, OnDestroy {
101108
* Recursively fetch unread notifications.
102109
*/
103110
fetchUnreadNotifications() {
104-
this.notificationsService.getNotifications(false, 9).subscribe((response: any) => {
105-
this.unreadNotifications = this.unreadNotifications.concat(response.pageItems);
106-
this.setNotifications();
107-
});
111+
this.notificationsService
112+
.getNotifications(false, 9)
113+
.pipe(takeUntil(this.destroy$))
114+
.subscribe((response: any) => {
115+
this.unreadNotifications = this.unreadNotifications.concat(response.pageItems);
116+
this.setNotifications();
117+
});
108118
// this.mockNotifications(); // Uncomment for Testing.
109119
this.timer = setTimeout(() => {
110120
this.fetchUnreadNotifications();
@@ -116,7 +126,10 @@ export class NotificationsTrayComponent implements OnInit, OnDestroy {
116126
*/
117127
menuClosed() {
118128
// Update the server for read notifications.
119-
this.notificationsService.updateNotifications().subscribe(() => {});
129+
this.notificationsService
130+
.updateNotifications()
131+
.pipe(takeUntil(this.destroy$))
132+
.subscribe(() => {});
120133
// Update locally for read notifications.
121134
this.readNotifications = this.unreadNotifications.concat(this.readNotifications);
122135
this.unreadNotifications = [];
@@ -127,9 +140,12 @@ export class NotificationsTrayComponent implements OnInit, OnDestroy {
127140
* Function to test notifications in case of faulty backend.
128141
*/
129142
mockNotifications() {
130-
this.notificationsService.getMockUnreadNotification().subscribe((response: any) => {
131-
this.unreadNotifications = this.unreadNotifications.concat(response.pageItems);
132-
this.setNotifications();
133-
});
143+
this.notificationsService
144+
.getMockUnreadNotification()
145+
.pipe(takeUntil(this.destroy$))
146+
.subscribe((response: any) => {
147+
this.unreadNotifications = this.unreadNotifications.concat(response.pageItems);
148+
this.setNotifications();
149+
});
134150
}
135151
}

0 commit comments

Comments
 (0)