From 2ba7e465794f9f6d71cd3fc3c16c73d28faab4a5 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sat, 23 May 2026 14:19:40 +0700 Subject: [PATCH] Task/improve type safety in home summary component (#6926) * fix(client): resolve errors * feat(client): replace constructor based DI with inject function * feat(client): enforce encapsulation * fix(client): remove dead code * feat(client): replace deprecated getDeviceInfo * feat(client): convert isLoading to signal * feat(client): convert hasImpersonationId to signal * feat(client): convert summary to signal * feat(client): convert user and hasPermissionToUpdateUserSettings to signals * feat(client): implement OnPush change detection strategy * fix(client): remove nested subscription --- .../home-summary/home-summary.component.ts | 102 ++++++++---------- .../components/home-summary/home-summary.html | 18 ++-- 2 files changed, 54 insertions(+), 66 deletions(-) diff --git a/apps/client/src/app/components/home-summary/home-summary.component.ts b/apps/client/src/app/components/home-summary/home-summary.component.ts index 60960480d..a63876a54 100644 --- a/apps/client/src/app/components/home-summary/home-summary.component.ts +++ b/apps/client/src/app/components/home-summary/home-summary.component.ts @@ -1,27 +1,27 @@ import { GfPortfolioSummaryComponent } from '@ghostfolio/client/components/portfolio-summary/portfolio-summary.component'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { - InfoItem, - PortfolioSummary, - User -} from '@ghostfolio/common/interfaces'; +import { PortfolioSummary, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { DataService } from '@ghostfolio/ui/services'; import { - ChangeDetectorRef, + ChangeDetectionStrategy, Component, + computed, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, - OnInit + inject, + OnInit, + signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCardModule } from '@angular/material/card'; -import { MatSnackBarRef, TextOnlySnackBar } from '@angular/material/snack-bar'; import { DeviceDetectorService } from 'ngx-device-detector'; +import { switchMap } from 'rxjs'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [GfPortfolioSummaryComponent, MatCardModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-home-summary', @@ -29,87 +29,75 @@ import { DeviceDetectorService } from 'ngx-device-detector'; templateUrl: './home-summary.html' }) export class GfHomeSummaryComponent implements OnInit { - public deviceType: string; - public hasImpersonationId: boolean; - public hasPermissionForSubscription: boolean; - public hasPermissionToUpdateUserSettings: boolean; - public info: InfoItem; - public isLoading = true; - public snackBarRef: MatSnackBarRef; - public summary: PortfolioSummary; - public user: User; + protected readonly hasImpersonationId = signal(false); + protected readonly isLoading = signal(true); + protected readonly summary = signal(undefined); + protected readonly user = signal(undefined); + + protected readonly deviceType = computed( + () => this.deviceDetectorService.deviceInfo().deviceType + ); + + protected readonly hasPermissionToUpdateUserSettings = computed(() => { + const user = this.user(); - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private destroyRef: DestroyRef, - private deviceDetectorService: DeviceDetectorService, - private impersonationStorageService: ImpersonationStorageService, - private userService: UserService - ) { - this.info = this.dataService.fetchInfo(); + return user + ? hasPermission(user.permissions, permissions.updateUserSettings) + : false; + }); - this.hasPermissionForSubscription = hasPermission( - this.info?.globalPermissions, - permissions.enableSubscription - ); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly deviceDetectorService = inject(DeviceDetectorService); + private readonly impersonationStorageService = inject( + ImpersonationStorageService + ); + private readonly userService = inject(UserService); + public constructor() { this.userService.stateChanged .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { - this.user = state.user; - - this.hasPermissionToUpdateUserSettings = hasPermission( - this.user.permissions, - permissions.updateUserSettings - ); - + this.user.set(state.user); this.update(); } }); } public ngOnInit() { - this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType; - this.impersonationStorageService .onChangeHasImpersonation() .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { - this.hasImpersonationId = !!impersonationId; + this.hasImpersonationId.set(!!impersonationId); }); } - public onChangeEmergencyFund(emergencyFund: number) { + protected onChangeEmergencyFund(emergencyFund: number) { this.dataService .putUserSetting({ emergencyFund }) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(() => { - this.userService - .get(true) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((user) => { - this.user = user; - - this.changeDetectorRef.markForCheck(); - }); + .pipe( + switchMap(() => this.userService.get(true)), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe((user) => { + this.user.set(user); }); } private update() { - this.isLoading = true; + this.isLoading.set(true); this.dataService .fetchPortfolioDetails() .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ summary }) => { - this.summary = summary; - this.isLoading = false; + if (summary) { + this.summary.set(summary); + } - this.changeDetectorRef.markForCheck(); + this.isLoading.set(false); }); - - this.changeDetectorRef.markForCheck(); } } diff --git a/apps/client/src/app/components/home-summary/home-summary.html b/apps/client/src/app/components/home-summary/home-summary.html index 2ed988e8a..a6da72b03 100644 --- a/apps/client/src/app/components/home-summary/home-summary.html +++ b/apps/client/src/app/components/home-summary/home-summary.html @@ -5,17 +5,17 @@