From 397f60756f1df6ce318603c0fbb631906ea867af Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 15 Mar 2026 08:55:44 +0100 Subject: [PATCH 1/2] Task/add destroyRef explicitly to lib components (#6559) * Add destroyRef explicitly --- .../activities-filter.component.ts | 5 ++-- .../fire-calculator.component.ts | 26 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/libs/ui/src/lib/activities-filter/activities-filter.component.ts b/libs/ui/src/lib/activities-filter/activities-filter.component.ts index 25fad683d..6b58e6aec 100644 --- a/libs/ui/src/lib/activities-filter/activities-filter.component.ts +++ b/libs/ui/src/lib/activities-filter/activities-filter.component.ts @@ -7,6 +7,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, + DestroyRef, ElementRef, Input, OnChanges, @@ -69,9 +70,9 @@ export class GfActivitiesFilterComponent implements OnChanges { protected selectedFilters: Filter[] = []; protected readonly separatorKeysCodes: number[] = [ENTER, COMMA]; - public constructor() { + public constructor(private destroyRef: DestroyRef) { this.searchControl.valueChanges - .pipe(takeUntilDestroyed()) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((filterOrSearchTerm) => { if (filterOrSearchTerm) { const searchTerm = diff --git a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts index 7a62564c6..c8e281609 100644 --- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts +++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts @@ -12,6 +12,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, ElementRef, Input, OnChanges, @@ -123,6 +124,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private fireCalculatorService: FireCalculatorService, private formBuilder: FormBuilder ) { @@ -135,13 +137,13 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { ); this.calculatorForm.valueChanges - .pipe(takeUntilDestroyed()) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.initialize(); }); this.calculatorForm.valueChanges - .pipe(debounceTime(500), takeUntilDestroyed()) + .pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef)) .subscribe(() => { const { projectedTotalAmount, retirementDate } = this.calculatorForm.getRawValue(); @@ -156,7 +158,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { this.calculatorForm .get('annualInterestRate') - ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((annualInterestRate) => { if (annualInterestRate !== null) { this.annualInterestRateChanged.emit(annualInterestRate); @@ -164,7 +169,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { }); this.calculatorForm .get('paymentPerPeriod') - ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((savingsRate) => { if (savingsRate !== null) { this.savingsRateChanged.emit(savingsRate); @@ -172,7 +180,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { }); this.calculatorForm .get('projectedTotalAmount') - ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((projectedTotalAmount) => { if (projectedTotalAmount !== null) { this.projectedTotalAmountChanged.emit(projectedTotalAmount); @@ -180,7 +191,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { }); this.calculatorForm .get('retirementDate') - ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((retirementDate) => { if (retirementDate !== null) { this.retirementDateChanged.emit(retirementDate); From a0424147d479cb6dbf06e75f4c2d0177b56cf220 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sun, 15 Mar 2026 14:56:38 +0700 Subject: [PATCH 2/2] Task/improve type safety of admin and data services (#6557) * Improve type safety --- libs/ui/src/lib/services/admin.service.ts | 24 +++++++++------- libs/ui/src/lib/services/data.service.ts | 35 +++++++++++++---------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/libs/ui/src/lib/services/admin.service.ts b/libs/ui/src/lib/services/admin.service.ts index 145f134e3..dec2a4dd5 100644 --- a/libs/ui/src/lib/services/admin.service.ts +++ b/libs/ui/src/lib/services/admin.service.ts @@ -21,24 +21,23 @@ import { Filter } from '@ghostfolio/common/interfaces'; import { DateRange } from '@ghostfolio/common/types'; -import { GF_ENVIRONMENT, GfEnvironment } from '@ghostfolio/ui/environment'; +import { GF_ENVIRONMENT } from '@ghostfolio/ui/environment'; import { DataService } from '@ghostfolio/ui/services'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; -import { Inject, Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { SortDirection } from '@angular/material/sort'; import { DataSource, MarketData, Platform } from '@prisma/client'; import { JobStatus } from 'bull'; +import { isNumber } from 'lodash'; @Injectable({ providedIn: 'root' }) export class AdminService { - public constructor( - private dataService: DataService, - @Inject(GF_ENVIRONMENT) private environment: GfEnvironment, - private http: HttpClient - ) {} + private readonly dataService = inject(DataService); + private readonly environment = inject(GF_ENVIRONMENT); + private readonly http = inject(HttpClient); public addAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) { return this.http.post( @@ -132,7 +131,7 @@ export class AdminService { public fetchJobs({ status }: { status?: JobStatus[] }) { let params = new HttpParams(); - if (status?.length > 0) { + if (status && status.length > 0) { params = params.append('status', status.join(',')); } @@ -158,8 +157,13 @@ export class AdminService { }) { let params = new HttpParams(); - params = params.append('skip', skip); - params = params.append('take', take); + if (isNumber(skip)) { + params = params.append('skip', skip); + } + + if (isNumber(take)) { + params = params.append('take', take); + } return this.http.get('/api/v1/admin/user', { params }); } diff --git a/libs/ui/src/lib/services/data.service.ts b/libs/ui/src/lib/services/data.service.ts index 169de067c..f3e3fb81f 100644 --- a/libs/ui/src/lib/services/data.service.ts +++ b/libs/ui/src/lib/services/data.service.ts @@ -65,16 +65,19 @@ import type { import { translate } from '@ghostfolio/ui/i18n'; import { HttpClient, HttpParams } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { SortDirection } from '@angular/material/sort'; import { utc } from '@date-fns/utc'; import { + Access as AccessModel, Account, AccountBalance, DataSource, MarketData, Order, - Tag + SymbolProfile, + Tag, + User as UserModel } from '@prisma/client'; import { format, parseISO } from 'date-fns'; import { cloneDeep, groupBy, isNumber } from 'lodash'; @@ -85,12 +88,12 @@ import { map } from 'rxjs/operators'; providedIn: 'root' }) export class DataService { - public constructor(private http: HttpClient) {} + private readonly http = inject(HttpClient); public buildFiltersAsQueryParams({ filters }: { filters?: Filter[] }) { let params = new HttpParams(); - if (filters?.length > 0) { + if (filters && filters.length > 0) { const { ACCOUNT: filtersByAccount, ASSET_CLASS: filtersByAssetClass, @@ -308,45 +311,47 @@ export class DataService { } public deleteAccess(aId: string) { - return this.http.delete(`/api/v1/access/${aId}`); + return this.http.delete(`/api/v1/access/${aId}`); } public deleteAccount(aId: string) { - return this.http.delete(`/api/v1/account/${aId}`); + return this.http.delete(`/api/v1/account/${aId}`); } public deleteAccountBalance(aId: string) { - return this.http.delete(`/api/v1/account-balance/${aId}`); + return this.http.delete(`/api/v1/account-balance/${aId}`); } - public deleteActivities({ filters }) { + public deleteActivities({ filters }: { filters?: Filter[] }) { const params = this.buildFiltersAsQueryParams({ filters }); - return this.http.delete('/api/v1/activities', { params }); + return this.http.delete('/api/v1/activities', { params }); } public deleteActivity(aId: string) { - return this.http.delete(`/api/v1/activities/${aId}`); + return this.http.delete(`/api/v1/activities/${aId}`); } public deleteBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { - return this.http.delete(`/api/v1/benchmarks/${dataSource}/${symbol}`); + return this.http.delete>( + `/api/v1/benchmarks/${dataSource}/${symbol}` + ); } public deleteOwnUser(aData: DeleteOwnUserDto) { - return this.http.delete(`/api/v1/user`, { body: aData }); + return this.http.delete(`/api/v1/user`, { body: aData }); } public deleteTag(aId: string) { - return this.http.delete(`/api/v1/tags/${aId}`); + return this.http.delete(`/api/v1/tags/${aId}`); } public deleteUser(aId: string) { - return this.http.delete(`/api/v1/user/${aId}`); + return this.http.delete(`/api/v1/user/${aId}`); } public deleteWatchlistItem({ dataSource, symbol }: AssetProfileIdentifier) { - return this.http.delete(`/api/v1/watchlist/${dataSource}/${symbol}`); + return this.http.delete(`/api/v1/watchlist/${dataSource}/${symbol}`); } public fetchAccesses() {