From 6c74aeb4360de92786e641dec82a4d85125a38f9 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:50:41 +0700 Subject: [PATCH] Task/improve type safety in multiple client components (#7302) Improve type safety --- .../home-holdings/home-holdings.component.ts | 51 +++++++----- .../user-account-settings.component.ts | 81 ++++++++++--------- .../user-detail-dialog.component.ts | 4 +- 3 files changed, 73 insertions(+), 63 deletions(-) diff --git a/apps/client/src/app/components/home-holdings/home-holdings.component.ts b/apps/client/src/app/components/home-holdings/home-holdings.component.ts index a789f3c66..cf7e52833 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.component.ts +++ b/apps/client/src/app/components/home-holdings/home-holdings.component.ts @@ -20,6 +20,7 @@ import { Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, + inject, OnInit } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -53,32 +54,34 @@ import { DeviceDetectorService } from 'ngx-device-detector'; export class GfHomeHoldingsComponent implements OnInit { public static DEFAULT_HOLDINGS_VIEW_MODE: HoldingsViewMode = 'TABLE'; - public deviceType: string; - public hasImpersonationId: boolean; - public hasPermissionToAccessHoldingsChart: boolean; - public hasPermissionToCreateActivity: boolean; - public holdings: PortfolioPosition[]; - public holdingType: HoldingType = 'ACTIVE'; - public holdingTypeOptions: ToggleOption[] = [ + protected deviceType: string; + protected hasImpersonationId: boolean; + protected hasPermissionToAccessHoldingsChart: boolean; + protected hasPermissionToCreateActivity: boolean; + protected holdings: PortfolioPosition[]; + protected holdingType: HoldingType = 'ACTIVE'; + protected readonly holdingTypeOptions: ToggleOption[] = [ { label: $localize`Active`, value: 'ACTIVE' }, { label: $localize`Closed`, value: 'CLOSED' } ]; - public routerLinkPortfolioActivities = + protected readonly routerLinkPortfolioActivities = internalRoutes.portfolio.subRoutes.activities.routerLink; - public user: User; - public viewModeFormControl = new FormControl( + protected user: User; + protected readonly viewModeFormControl = new FormControl( GfHomeHoldingsComponent.DEFAULT_HOLDINGS_VIEW_MODE ); - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private destroyRef: DestroyRef, - private deviceDetectorService: DeviceDetectorService, - private impersonationStorageService: ImpersonationStorageService, - private router: Router, - private userService: UserService - ) { + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly deviceDetectorService = inject(DeviceDetectorService); + private readonly impersonationStorageService = inject( + ImpersonationStorageService + ); + private readonly router = inject(Router); + private readonly userService = inject(UserService); + + public constructor() { addIcons({ gridOutline, reorderFourOutline }); } @@ -119,6 +122,10 @@ export class GfHomeHoldingsComponent implements OnInit { this.viewModeFormControl.valueChanges .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((holdingsViewMode) => { + if (!holdingsViewMode) { + return; + } + this.dataService .putUserSetting({ holdingsViewMode }) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -135,13 +142,13 @@ export class GfHomeHoldingsComponent implements OnInit { }); } - public onChangeHoldingType(aHoldingType: HoldingType) { + protected onChangeHoldingType(aHoldingType: HoldingType) { this.holdingType = aHoldingType; this.initialize(); } - public onHoldingClicked({ dataSource, symbol }: AssetProfileIdentifier) { + protected onHoldingClicked({ dataSource, symbol }: AssetProfileIdentifier) { if (dataSource && symbol) { this.router.navigate([], { queryParams: { dataSource, symbol, holdingDetailDialog: true } @@ -185,7 +192,7 @@ export class GfHomeHoldingsComponent implements OnInit { ); } - this.holdings = undefined; + this.holdings = []; this.fetchHoldings() .pipe(takeUntilDestroyed(this.destroyRef)) diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts index c8210b0c3..c9626b188 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -20,11 +20,12 @@ import { Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, + inject, OnInit } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { - FormBuilder, + NonNullableFormBuilder, FormsModule, ReactiveFormsModule, Validators @@ -69,22 +70,22 @@ import { catchError } from 'rxjs/operators'; templateUrl: './user-account-settings.html' }) export class GfUserAccountSettingsComponent implements OnInit { - public appearancePlaceholder = $localize`Auto`; - public baseCurrency: string; - public closeUserAccountMail: string; - public currencies: string[] = []; - public deleteOwnUserForm = this.formBuilder.group({ + protected readonly appearancePlaceholder = $localize`Auto`; + protected readonly baseCurrency: string; + protected closeUserAccountMail: string; + protected readonly currencies: string[] = []; + protected readonly deleteOwnUserForm = inject(NonNullableFormBuilder).group({ accessToken: ['', Validators.required] }); - public hasPermissionToDeleteOwnUser: boolean; - public hasPermissionToRequestOwnUserDeletion: boolean; - public hasPermissionToUpdateViewMode: boolean; - public hasPermissionToUpdateUserSettings: boolean; - public isAccessTokenHidden = true; - public isFingerprintSupported = this.doesBrowserSupportAuthn(); - public isWebAuthnEnabled: boolean; - public language = document.documentElement.lang; - public locales = [ + protected hasPermissionToDeleteOwnUser: boolean; + protected hasPermissionToRequestOwnUserDeletion: boolean; + protected hasPermissionToUpdateViewMode: boolean; + protected hasPermissionToUpdateUserSettings: boolean; + protected isAccessTokenHidden = true; + protected readonly isFingerprintSupported = this.doesBrowserSupportAuthn(); + protected isWebAuthnEnabled: boolean; + protected readonly language = document.documentElement.lang; + protected locales = [ 'ca', 'de', 'de-CH', @@ -102,19 +103,18 @@ export class GfUserAccountSettingsComponent implements OnInit { 'uk', 'zh' ]; - public user: User; - - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private destroyRef: DestroyRef, - private formBuilder: FormBuilder, - private notificationService: NotificationService, - private settingsStorageService: SettingsStorageService, - private snackBar: MatSnackBar, - private userService: UserService, - public webAuthnService: WebAuthnService - ) { + protected user: User; + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly notificationService = inject(NotificationService); + private readonly settingsStorageService = inject(SettingsStorageService); + private readonly snackBar = inject(MatSnackBar); + private readonly userService = inject(UserService); + private readonly webAuthnService = inject(WebAuthnService); + + public constructor() { const { baseCurrency, currencies } = this.dataService.fetchInfo(); this.baseCurrency = baseCurrency; @@ -148,7 +148,10 @@ export class GfUserAccountSettingsComponent implements OnInit { permissions.updateViewMode ); - this.locales.push(this.user.settings.locale); + if (this.user.settings.locale) { + this.locales.push(this.user.settings.locale); + } + this.locales = Array.from(new Set(this.locales)).sort(); this.changeDetectorRef.markForCheck(); @@ -162,11 +165,11 @@ export class GfUserAccountSettingsComponent implements OnInit { this.update(); } - public isCommunityLanguage() { + protected isCommunityLanguage() { return !['de', 'en'].includes(this.language); } - public onChangeUserSetting(aKey: string, aValue: string) { + protected onChangeUserSetting(aKey: string, aValue: string) { this.dataService .putUserSetting({ [aKey]: aValue }) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -190,12 +193,12 @@ export class GfUserAccountSettingsComponent implements OnInit { }); } - public onCloseAccount() { + protected onCloseAccount() { this.notificationService.confirm({ confirmFn: () => { this.dataService .deleteOwnUser({ - accessToken: this.deleteOwnUserForm.get('accessToken').value + accessToken: this.deleteOwnUserForm.controls.accessToken.value }) .pipe( catchError(() => { @@ -218,7 +221,7 @@ export class GfUserAccountSettingsComponent implements OnInit { }); } - public onExperimentalFeaturesChange(aEvent: MatSlideToggleChange) { + protected onExperimentalFeaturesChange(aEvent: MatSlideToggleChange) { this.dataService .putUserSetting({ isExperimentalFeatures: aEvent.checked }) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -234,13 +237,13 @@ export class GfUserAccountSettingsComponent implements OnInit { }); } - public onExport() { + protected onExport() { this.dataService .fetchExport() .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { for (const activity of data.activities) { - delete activity.id; + delete (activity as Omit & { id?: string }).id; } downloadAsFile({ @@ -254,7 +257,7 @@ export class GfUserAccountSettingsComponent implements OnInit { }); } - public onRestrictedViewChange(aEvent: MatSlideToggleChange) { + protected onRestrictedViewChange(aEvent: MatSlideToggleChange) { this.dataService .putUserSetting({ isRestrictedView: aEvent.checked }) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -270,7 +273,7 @@ export class GfUserAccountSettingsComponent implements OnInit { }); } - public async onSignInWithFingerprintChange(aEvent: MatSlideToggleChange) { + protected async onSignInWithFingerprintChange(aEvent: MatSlideToggleChange) { if (aEvent.checked) { try { await this.registerDevice(); @@ -293,7 +296,7 @@ export class GfUserAccountSettingsComponent implements OnInit { } } - public onViewModeChange(aEvent: MatSlideToggleChange) { + protected onViewModeChange(aEvent: MatSlideToggleChange) { this.dataService .putUserSetting({ viewMode: aEvent.checked === true ? 'ZEN' : 'DEFAULT' }) .pipe(takeUntilDestroyed(this.destroyRef)) diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts index 637fe8c31..04d89c5a6 100644 --- a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts @@ -49,7 +49,7 @@ import { templateUrl: './user-detail-dialog.html' }) export class GfUserDetailDialogComponent implements OnInit { - protected baseCurrency: string; + protected readonly baseCurrency: string; protected readonly getCountryName = getCountryName; protected readonly subscriptionsDataSource = new MatTableDataSource(); @@ -114,7 +114,7 @@ export class GfUserDetailDialogComponent implements OnInit { return price !== null; }) .map(({ price }) => { - return new Big(price); + return new Big(price ?? 0); }) ).toNumber(); }