From 980060a49f05b37921d97e37d1801654260aa732 Mon Sep 17 00:00:00 2001 From: Erwin <111194281+Erwin-N@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:52:56 +0100 Subject: [PATCH 1/7] Task/eliminate OnDestroy lifecycle hook from home watchlist component (#6551) * Eliminate OnDestroy lifecycle hook From f469bb9d363ba792052eb2de03d25bebda813ce8 Mon Sep 17 00:00:00 2001 From: Erwin <111194281+Erwin-N@users.noreply.github.com> Date: Sat, 14 Mar 2026 16:00:25 +0100 Subject: [PATCH 2/7] Task/eliminate OnDestroy lifecycle hook from admin platform component (#6553) * Eliminate OnDestroy lifecycle hook --- .../admin-platform.component.ts | 34 ++++++++----------- ...ate-or-update-platform-dialog.component.ts | 17 ++-------- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts index 2a04f86cc..b8f2af789 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts @@ -11,11 +11,12 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, Input, - OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; @@ -32,7 +33,6 @@ import { } from 'ionicons/icons'; import { get } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject, takeUntil } from 'rxjs'; import { GfCreateOrUpdatePlatformDialogComponent } from './create-or-update-platform-dialog/create-or-update-platform-dialog.component'; import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform-dialog/interfaces/interfaces'; @@ -53,7 +53,7 @@ import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform- styleUrls: ['./admin-platform.component.scss'], templateUrl: './admin-platform.component.html' }) -export class GfAdminPlatformComponent implements OnDestroy, OnInit { +export class GfAdminPlatformComponent implements OnInit { @Input() locale = getLocale(); @ViewChild(MatSort) sort: MatSort; @@ -63,12 +63,11 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { public displayedColumns = ['name', 'url', 'accounts', 'actions']; public platforms: Platform[]; - private unsubscribeSubject = new Subject(); - public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private notificationService: NotificationService, @@ -77,7 +76,7 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { private userService: UserService ) { this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['createPlatformDialog']) { this.openCreatePlatformDialog(); @@ -119,20 +118,15 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private deletePlatform(aId: string) { this.adminService .deletePlatform(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchPlatforms(); @@ -143,7 +137,7 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { private fetchPlatforms() { this.adminService .fetchPlatforms() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((platforms) => { this.platforms = platforms; @@ -175,17 +169,17 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((platform: CreatePlatformDto | null) => { if (platform) { this.adminService .postPlatform(platform) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchPlatforms(); @@ -223,17 +217,17 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((platform: UpdatePlatformDto | null) => { if (platform) { this.adminService .putPlatform(platform) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchPlatforms(); diff --git a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts index dfcf300c1..0cfe026a4 100644 --- a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts +++ b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts @@ -2,12 +2,7 @@ import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { validateObjectForForm } from '@ghostfolio/common/utils'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; -import { - ChangeDetectionStrategy, - Component, - Inject, - OnDestroy -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { FormBuilder, FormGroup, @@ -23,7 +18,6 @@ import { } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; -import { Subject } from 'rxjs'; import { CreateOrUpdatePlatformDialogParams } from './interfaces/interfaces'; @@ -43,11 +37,9 @@ import { CreateOrUpdatePlatformDialogParams } from './interfaces/interfaces'; styleUrls: ['./create-or-update-platform-dialog.scss'], templateUrl: 'create-or-update-platform-dialog.html' }) -export class GfCreateOrUpdatePlatformDialogComponent implements OnDestroy { +export class GfCreateOrUpdatePlatformDialogComponent { public platformForm: FormGroup; - private unsubscribeSubject = new Subject(); - public constructor( @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdatePlatformDialogParams, public dialogRef: MatDialogRef, @@ -90,9 +82,4 @@ export class GfCreateOrUpdatePlatformDialogComponent implements OnDestroy { console.error(error); } } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } From 319bde18166015b9654bbf75c4c495052df06fe7 Mon Sep 17 00:00:00 2001 From: Erwin <111194281+Erwin-N@users.noreply.github.com> Date: Sat, 14 Mar 2026 16:03:07 +0100 Subject: [PATCH 3/7] Task/eliminate OnDestroy lifecycle hook from auth page (#6528) * Eliminate OnDestroy lifecycle hook --- .../src/app/pages/auth/auth-page.component.ts | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/apps/client/src/app/pages/auth/auth-page.component.ts b/apps/client/src/app/pages/auth/auth-page.component.ts index 082401d6d..1b0b4a67c 100644 --- a/apps/client/src/app/pages/auth/auth-page.component.ts +++ b/apps/client/src/app/pages/auth/auth-page.component.ts @@ -4,20 +4,18 @@ import { } from '@ghostfolio/client/services/settings-storage.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'gf-auth-page', styleUrls: ['./auth-page.scss'], templateUrl: './auth-page.html' }) -export class GfAuthPageComponent implements OnDestroy, OnInit { - private unsubscribeSubject = new Subject(); - +export class GfAuthPageComponent implements OnInit { public constructor( + private destroyRef: DestroyRef, private route: ActivatedRoute, private router: Router, private settingsStorageService: SettingsStorageService, @@ -26,7 +24,7 @@ export class GfAuthPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.route.params - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { const jwt = params['jwt']; @@ -38,9 +36,4 @@ export class GfAuthPageComponent implements OnDestroy, OnInit { this.router.navigate(['/']); }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } From 2f34c7d471a7b8e2eef4e72c6af77f82426157f0 Mon Sep 17 00:00:00 2001 From: Erwin <111194281+Erwin-N@users.noreply.github.com> Date: Sat, 14 Mar 2026 16:09:39 +0100 Subject: [PATCH 4/7] Task/eliminate OnDestroy lifecycle hook from admin market data component (#6552) * Eliminate OnDestroy lifecycle hook --- .../admin-market-data.component.ts | 45 ++++++++----------- .../asset-profile-dialog.component.ts | 37 +++++++-------- .../create-asset-profile-dialog.component.ts | 18 +++----- 3 files changed, 42 insertions(+), 58 deletions(-) diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index 0beca6f3c..d3265946f 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -26,10 +26,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDialog } from '@angular/material/dialog'; @@ -63,7 +64,7 @@ import { import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject } from 'rxjs'; -import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; +import { distinctUntilChanged } from 'rxjs/operators'; import { AdminMarketDataService } from './admin-market-data.service'; import { GfAssetProfileDialogComponent } from './asset-profile-dialog/asset-profile-dialog.component'; @@ -95,9 +96,7 @@ import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/in styleUrls: ['./admin-market-data.scss'], templateUrl: './admin-market-data.html' }) -export class GfAdminMarketDataComponent - implements AfterViewInit, OnDestroy, OnInit -{ +export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -166,13 +165,12 @@ export class GfAdminMarketDataComponent public totalItems = 0; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( public adminMarketDataService: AdminMarketDataService, private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private route: ActivatedRoute, @@ -209,7 +207,7 @@ export class GfAdminMarketDataComponent this.displayedColumns.push('actions'); this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if ( params['assetProfileDialog'] && @@ -226,7 +224,7 @@ export class GfAdminMarketDataComponent }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -238,7 +236,7 @@ export class GfAdminMarketDataComponent }); this.filters$ - .pipe(distinctUntilChanged(), takeUntil(this.unsubscribeSubject)) + .pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)) .subscribe((filters) => { this.activeFilters = filters; @@ -302,7 +300,7 @@ export class GfAdminMarketDataComponent public onGather7Days() { this.adminService .gather7Days() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { setTimeout(() => { window.location.reload(); @@ -313,7 +311,7 @@ export class GfAdminMarketDataComponent public onGatherMax() { this.adminService .gatherMax() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { setTimeout(() => { window.location.reload(); @@ -324,7 +322,7 @@ export class GfAdminMarketDataComponent public onGatherProfileData() { this.adminService .gatherProfileData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -334,14 +332,14 @@ export class GfAdminMarketDataComponent }: AssetProfileIdentifier) { this.adminService .gatherProfileDataBySymbol({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } public onGatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) { this.adminService .gatherSymbol({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -358,11 +356,6 @@ export class GfAdminMarketDataComponent }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private loadData( { pageIndex, @@ -399,7 +392,7 @@ export class GfAdminMarketDataComponent skip: pageIndex * this.pageSize, take: this.pageSize }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ count, marketData }) => { this.totalItems = count; @@ -430,7 +423,7 @@ export class GfAdminMarketDataComponent }) { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -452,7 +445,7 @@ export class GfAdminMarketDataComponent dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe( (newAssetProfileIdentifier: AssetProfileIdentifier | undefined) => { if (newAssetProfileIdentifier) { @@ -468,7 +461,7 @@ export class GfAdminMarketDataComponent private openCreateAssetProfileDialog() { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -486,7 +479,7 @@ export class GfAdminMarketDataComponent dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((result) => { if (!result) { this.router.navigate(['.'], { relativeTo: this.route }); @@ -499,7 +492,7 @@ export class GfAdminMarketDataComponent if (addAssetProfile && dataSource && symbol) { this.adminService .addAssetProfile({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.loadData(); }); diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index b1519e35b..c0af46e22 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -37,12 +37,13 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, ElementRef, Inject, - OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { AbstractControl, FormBuilder, @@ -87,8 +88,8 @@ import { serverOutline } from 'ionicons/icons'; import ms from 'ms'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError } from 'rxjs/operators'; import { AssetProfileDialogParams } from './interfaces/interfaces'; @@ -121,7 +122,7 @@ import { AssetProfileDialogParams } from './interfaces/interfaces'; styleUrls: ['./asset-profile-dialog.component.scss'], templateUrl: 'asset-profile-dialog.html' }) -export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { +export class GfAssetProfileDialogComponent implements OnInit { private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format( new Date(), DATE_FORMAT @@ -241,14 +242,13 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { public user: User; - private unsubscribeSubject = new Subject(); - public constructor( public adminMarketDataService: AdminMarketDataService, private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams, private dataService: DataService, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef, private formBuilder: FormBuilder, private notificationService: NotificationService, @@ -282,7 +282,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { this.adminService .fetchAdminData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ settings }) => { this.isDataGatheringEnabled = settings[PROPERTY_IS_DATA_GATHERING_ENABLED] === false ? false : true; @@ -291,7 +291,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -300,7 +300,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { this.assetProfileForm .get('assetClass') - .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) + .valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((assetClass) => { const assetSubClasses = ASSET_CLASS_MAPPING.get(assetClass) ?? []; @@ -323,7 +323,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { dataSource: this.data.dataSource, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ assetProfile, marketData }) => { this.assetProfile = assetProfile; @@ -436,7 +436,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }: AssetProfileIdentifier) { this.adminService .gatherProfileDataBySymbol({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -449,7 +449,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { } & AssetProfileIdentifier) { this.adminService .gatherSymbol({ dataSource, range, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -462,7 +462,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { public onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { this.dataService .postBenchmark({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.dataService.updateInfo(); @@ -664,7 +664,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { const newAssetProfileIdentifier = { @@ -714,7 +714,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }); return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(({ price }) => { this.notificationService.alert({ @@ -745,7 +745,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { public onUnsetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { this.dataService .deleteBenchmark({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.dataService.updateInfo(); @@ -755,11 +755,6 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - public onTriggerSubmitAssetProfileForm() { if (this.assetProfileForm.valid) { this.onSubmitAssetProfileForm(); diff --git a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts index fbf8afa03..35887abb4 100644 --- a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts @@ -10,9 +10,10 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { AbstractControl, FormBuilder, @@ -31,7 +32,7 @@ import { MatInputModule } from '@angular/material/input'; import { MatRadioModule } from '@angular/material/radio'; import { DataSource } from '@prisma/client'; import { isISO4217CurrencyCode } from 'class-validator'; -import { Subject, switchMap, takeUntil } from 'rxjs'; +import { switchMap } from 'rxjs'; import { CreateAssetProfileDialogMode } from './interfaces/interfaces'; @@ -52,19 +53,19 @@ import { CreateAssetProfileDialogMode } from './interfaces/interfaces'; styleUrls: ['./create-asset-profile-dialog.component.scss'], templateUrl: 'create-asset-profile-dialog.html' }) -export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { +export class GfCreateAssetProfileDialogComponent implements OnInit { public createAssetProfileForm: FormGroup; public ghostfolioPrefix = `${ghostfolioPrefix}_`; public mode: CreateAssetProfileDialogMode; private customCurrencies: string[]; private dataSourceForExchangeRates: DataSource; - private unsubscribeSubject = new Subject(); public constructor( public readonly adminService: AdminService, private readonly changeDetectorRef: ChangeDetectorRef, private readonly dataService: DataService, + private readonly destroyRef: DestroyRef, public readonly dialogRef: MatDialogRef, public readonly formBuilder: FormBuilder ) {} @@ -125,7 +126,7 @@ export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { symbol: `${DEFAULT_CURRENCY}${currency}` }); }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { this.dialogRef.close({ @@ -154,11 +155,6 @@ export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { return false; } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private atLeastOneValid(control: AbstractControl): ValidationErrors { const addCurrencyControl = control.get('addCurrency'); const addSymbolControl = control.get('addSymbol'); @@ -189,7 +185,7 @@ export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { private initialize() { this.adminService .fetchAdminData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ dataProviders, settings }) => { this.customCurrencies = settings[PROPERTY_CURRENCIES] as string[]; From 2b072391cc7dfb9a29ecbdc50075ed96a365d4e2 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 14 Mar 2026 16:17:17 +0100 Subject: [PATCH 5/7] Task/upgrade @ionic/angular to version 8.8.1 (#6558) * Upgrade @ionic/angular to version 8.8.1 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 27 +++++++++++++++------------ package.json | 2 +- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13f014146..fee3ecfa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Consolidated the sign-out logic within the user service to unify cookie, state and token clearance - Improved the language localization for Polish (`pl`) +- Upgraded `@ionic/angular` from version `8.7.3` to `8.8.1` - Upgraded `svgmap` from version `2.14.0` to `2.19.2` ## 2.249.0 - 2026-03-10 diff --git a/package-lock.json b/package-lock.json index 9ae63af6e..e77522a6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@codewithdan/observable-store": "2.2.15", "@date-fns/utc": "2.1.1", "@internationalized/number": "3.6.5", - "@ionic/angular": "8.7.8", + "@ionic/angular": "8.8.1", "@keyv/redis": "4.4.0", "@nestjs/bull": "11.0.4", "@nestjs/cache-manager": "3.1.0", @@ -5070,12 +5070,12 @@ } }, "node_modules/@ionic/angular": { - "version": "8.7.8", - "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-8.7.8.tgz", - "integrity": "sha512-IBN5h3nIOwbuglLit48S7wNeg7NHtl/vaKAHDggICyzI92cSg5yYL07Fz59pszhkBlZQUB5SQnml990Zj2bZUg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-8.8.1.tgz", + "integrity": "sha512-Jp7LbouSHAnR00Dsa8qE1CSOZNqAfBCO0XKXScJNz8NKVoZe5fPGy/CboehGtAQ1xgzh2eDa15zMmyetXjAkYA==", "license": "MIT", "dependencies": { - "@ionic/core": "8.7.8", + "@ionic/core": "8.8.1", "ionicons": "^8.0.13", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" @@ -5089,14 +5089,17 @@ } }, "node_modules/@ionic/core": { - "version": "8.7.8", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.8.tgz", - "integrity": "sha512-GLWb/lz3kocpzTZTeQQ5xxoWz4CKHD6zpnbwJknTKsncebohAaw2KTe7uOw5toKQEDdohTseFuSGoDDBoRQ1Ug==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.1.tgz", + "integrity": "sha512-ksOUHyOEqoyUIVWcwCNSFZVGwNfP1DKrUVeN/Cdk/Xl9Rdd/5MLHGsrOQpWQfoCf3Csdnw+KHHPrXz/2fzMkMA==", "license": "MIT", "dependencies": { - "@stencil/core": "4.38.0", + "@stencil/core": "4.43.0", "ionicons": "^8.0.13", "tslib": "^2.1.0" + }, + "engines": { + "node": ">= 16" } }, "node_modules/@ioredis/commands": { @@ -11930,9 +11933,9 @@ "license": "MIT" }, "node_modules/@stencil/core": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.38.0.tgz", - "integrity": "sha512-oC3QFKO0X1yXVvETgc8OLY525MNKhn9vISBrbtKnGoPlokJ6rI8Vk1RK22TevnNrHLI4SExNLbcDnqilKR35JQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.43.0.tgz", + "integrity": "sha512-6Uj2Z3lzLuufYAE7asZ6NLKgSwsB9uxl84Eh34PASnUjfj32GkrP4DtKK7fNeh1WFGGyffsTDka3gwtl+4reUg==", "license": "MIT", "bin": { "stencil": "bin/stencil" diff --git a/package.json b/package.json index fd62729b9..e8b38049b 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@codewithdan/observable-store": "2.2.15", "@date-fns/utc": "2.1.1", "@internationalized/number": "3.6.5", - "@ionic/angular": "8.7.8", + "@ionic/angular": "8.8.1", "@keyv/redis": "4.4.0", "@nestjs/bull": "11.0.4", "@nestjs/cache-manager": "3.1.0", From 89b41da17f29484fb4148bc35aa76388227006c6 Mon Sep 17 00:00:00 2001 From: Erwin <111194281+Erwin-N@users.noreply.github.com> Date: Sat, 14 Mar 2026 16:58:37 +0100 Subject: [PATCH 6/7] Task/eliminate OnDestroy lifecycle hook from admin tag component (#6561) * Eliminate OnDestroy lifecycle hook --- .../admin-tag/admin-tag.component.ts | 34 ++++++++----------- .../create-or-update-tag-dialog.component.ts | 17 ++-------- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.ts b/apps/client/src/app/components/admin-tag/admin-tag.component.ts index 0b64a4ca1..506736156 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.ts +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.ts @@ -10,11 +10,12 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, Input, - OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; @@ -31,7 +32,6 @@ import { } from 'ionicons/icons'; import { get } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject, takeUntil } from 'rxjs'; import { GfCreateOrUpdateTagDialogComponent } from './create-or-update-tag-dialog/create-or-update-tag-dialog.component'; import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/interfaces/interfaces'; @@ -51,7 +51,7 @@ import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/int styleUrls: ['./admin-tag.component.scss'], templateUrl: './admin-tag.component.html' }) -export class GfAdminTagComponent implements OnDestroy, OnInit { +export class GfAdminTagComponent implements OnInit { @Input() locale = getLocale(); @ViewChild(MatSort) sort: MatSort; @@ -61,11 +61,10 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { public displayedColumns = ['name', 'userId', 'activities', 'actions']; public tags: Tag[]; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private notificationService: NotificationService, @@ -74,7 +73,7 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { private userService: UserService ) { this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['createTagDialog']) { this.openCreateTagDialog(); @@ -116,20 +115,15 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private deleteTag(aId: string) { this.dataService .deleteTag(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchTags(); @@ -140,7 +134,7 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { private fetchTags() { this.dataService .fetchTags() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tags) => { this.tags = tags; @@ -171,17 +165,17 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tag: CreateTagDto | null) => { if (tag) { this.dataService .postTag(tag) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchTags(); @@ -210,17 +204,17 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tag: UpdateTagDto | null) => { if (tag) { this.dataService .putTag(tag) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchTags(); diff --git a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts index 323609a48..e22c73478 100644 --- a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts +++ b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts @@ -1,12 +1,7 @@ import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { validateObjectForForm } from '@ghostfolio/common/utils'; -import { - ChangeDetectionStrategy, - Component, - Inject, - OnDestroy -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { FormBuilder, FormGroup, @@ -21,7 +16,6 @@ import { } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; -import { Subject } from 'rxjs'; import { CreateOrUpdateTagDialogParams } from './interfaces/interfaces'; @@ -40,11 +34,9 @@ import { CreateOrUpdateTagDialogParams } from './interfaces/interfaces'; styleUrls: ['./create-or-update-tag-dialog.scss'], templateUrl: 'create-or-update-tag-dialog.html' }) -export class GfCreateOrUpdateTagDialogComponent implements OnDestroy { +export class GfCreateOrUpdateTagDialogComponent { public tagForm: FormGroup; - private unsubscribeSubject = new Subject(); - public constructor( @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateTagDialogParams, public dialogRef: MatDialogRef, @@ -85,9 +77,4 @@ export class GfCreateOrUpdateTagDialogComponent implements OnDestroy { console.error(error); } } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } From 19315e8ca528c38f8b04a06eef3c174c7d8f0f0b Mon Sep 17 00:00:00 2001 From: Erwin <111194281+Erwin-N@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:00:07 +0100 Subject: [PATCH 7/7] Task/eliminate OnDestroy lifecycle hook from admin settings component (#6560) * Eliminate OnDestroy lifecycle hook --- .../admin-settings.component.ts | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.ts b/apps/client/src/app/components/admin-settings/admin-settings.component.ts index b8cd3409d..d030abb82 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.ts +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -22,10 +22,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatMenuModule } from '@angular/material/menu'; @@ -38,7 +39,7 @@ import { addIcons } from 'ionicons'; import { ellipsisHorizontal, trashOutline } from 'ionicons/icons'; import { get } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { catchError, filter, of, Subject, takeUntil } from 'rxjs'; +import { catchError, filter, of } from 'rxjs'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -64,7 +65,7 @@ import { catchError, filter, of, Subject, takeUntil } from 'rxjs'; styleUrls: ['./admin-settings.component.scss'], templateUrl: './admin-settings.component.html' }) -export class GfAdminSettingsComponent implements OnDestroy, OnInit { +export class GfAdminSettingsComponent implements OnInit { @ViewChild(MatSort) sort: MatSort; public dataSource = new MatTableDataSource(); @@ -82,12 +83,11 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { public pricingUrl: string; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private notificationService: NotificationService, private userService: UserService ) { @@ -96,7 +96,7 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { public ngOnInit() { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -153,11 +153,6 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private initialize() { this.isLoading = true; @@ -165,7 +160,7 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { this.adminService .fetchAdminData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ dataProviders, settings }) => { const filteredProviders = dataProviders.filter(({ dataSource }) => { return dataSource !== 'MANUAL'; @@ -193,7 +188,7 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { filter((status) => { return status !== null; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe((status) => { this.ghostfolioApiStatus = status;