Browse Source

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
pull/6928/head
Kenrick Tandrian 2 days ago
committed by GitHub
parent
commit
2ba7e46579
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 102
      apps/client/src/app/components/home-summary/home-summary.component.ts
  2. 18
      apps/client/src/app/components/home-summary/home-summary.html

102
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 { GfPortfolioSummaryComponent } from '@ghostfolio/client/components/portfolio-summary/portfolio-summary.component';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { import { PortfolioSummary, User } from '@ghostfolio/common/interfaces';
InfoItem,
PortfolioSummary,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DataService } from '@ghostfolio/ui/services'; import { DataService } from '@ghostfolio/ui/services';
import { import {
ChangeDetectorRef, ChangeDetectionStrategy,
Component, Component,
computed,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
DestroyRef, DestroyRef,
OnInit inject,
OnInit,
signal
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatSnackBarRef, TextOnlySnackBar } from '@angular/material/snack-bar';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { switchMap } from 'rxjs';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [GfPortfolioSummaryComponent, MatCardModule], imports: [GfPortfolioSummaryComponent, MatCardModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-home-summary', selector: 'gf-home-summary',
@ -29,87 +29,75 @@ import { DeviceDetectorService } from 'ngx-device-detector';
templateUrl: './home-summary.html' templateUrl: './home-summary.html'
}) })
export class GfHomeSummaryComponent implements OnInit { export class GfHomeSummaryComponent implements OnInit {
public deviceType: string; protected readonly hasImpersonationId = signal<boolean>(false);
public hasImpersonationId: boolean; protected readonly isLoading = signal(true);
public hasPermissionForSubscription: boolean; protected readonly summary = signal<PortfolioSummary | undefined>(undefined);
public hasPermissionToUpdateUserSettings: boolean; protected readonly user = signal<User | undefined>(undefined);
public info: InfoItem;
public isLoading = true; protected readonly deviceType = computed(
public snackBarRef: MatSnackBarRef<TextOnlySnackBar>; () => this.deviceDetectorService.deviceInfo().deviceType
public summary: PortfolioSummary; );
public user: User;
protected readonly hasPermissionToUpdateUserSettings = computed(() => {
const user = this.user();
public constructor( return user
private changeDetectorRef: ChangeDetectorRef, ? hasPermission(user.permissions, permissions.updateUserSettings)
private dataService: DataService, : false;
private destroyRef: DestroyRef, });
private deviceDetectorService: DeviceDetectorService,
private impersonationStorageService: ImpersonationStorageService,
private userService: UserService
) {
this.info = this.dataService.fetchInfo();
this.hasPermissionForSubscription = hasPermission( private readonly dataService = inject(DataService);
this.info?.globalPermissions, private readonly destroyRef = inject(DestroyRef);
permissions.enableSubscription private readonly deviceDetectorService = inject(DeviceDetectorService);
); private readonly impersonationStorageService = inject(
ImpersonationStorageService
);
private readonly userService = inject(UserService);
public constructor() {
this.userService.stateChanged this.userService.stateChanged
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((state) => { .subscribe((state) => {
if (state?.user) { if (state?.user) {
this.user = state.user; this.user.set(state.user);
this.hasPermissionToUpdateUserSettings = hasPermission(
this.user.permissions,
permissions.updateUserSettings
);
this.update(); this.update();
} }
}); });
} }
public ngOnInit() { public ngOnInit() {
this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType;
this.impersonationStorageService this.impersonationStorageService
.onChangeHasImpersonation() .onChangeHasImpersonation()
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((impersonationId) => { .subscribe((impersonationId) => {
this.hasImpersonationId = !!impersonationId; this.hasImpersonationId.set(!!impersonationId);
}); });
} }
public onChangeEmergencyFund(emergencyFund: number) { protected onChangeEmergencyFund(emergencyFund: number) {
this.dataService this.dataService
.putUserSetting({ emergencyFund }) .putUserSetting({ emergencyFund })
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(
.subscribe(() => { switchMap(() => this.userService.get(true)),
this.userService takeUntilDestroyed(this.destroyRef)
.get(true) )
.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => {
.subscribe((user) => { this.user.set(user);
this.user = user;
this.changeDetectorRef.markForCheck();
});
}); });
} }
private update() { private update() {
this.isLoading = true; this.isLoading.set(true);
this.dataService this.dataService
.fetchPortfolioDetails() .fetchPortfolioDetails()
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ summary }) => { .subscribe(({ summary }) => {
this.summary = summary; if (summary) {
this.isLoading = false; this.summary.set(summary);
}
this.changeDetectorRef.markForCheck(); this.isLoading.set(false);
}); });
this.changeDetectorRef.markForCheck();
} }
} }

18
apps/client/src/app/components/home-summary/home-summary.html

@ -5,17 +5,17 @@
<mat-card appearance="outlined"> <mat-card appearance="outlined">
<mat-card-content> <mat-card-content>
<gf-portfolio-summary <gf-portfolio-summary
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user()?.settings?.baseCurrency"
[deviceType]="deviceType" [deviceType]="deviceType()"
[hasImpersonationId]="hasImpersonationId" [hasImpersonationId]="hasImpersonationId()"
[hasPermissionToUpdateUserSettings]=" [hasPermissionToUpdateUserSettings]="
!hasImpersonationId && hasPermissionToUpdateUserSettings !hasImpersonationId() && hasPermissionToUpdateUserSettings()
" "
[isLoading]="isLoading" [isLoading]="isLoading()"
[language]="user?.settings?.language" [language]="user()?.settings?.language"
[locale]="user?.settings?.locale" [locale]="user()?.settings?.locale"
[summary]="summary" [summary]="summary()"
[user]="user" [user]="user()"
(emergencyFundChanged)="onChangeEmergencyFund($event)" (emergencyFundChanged)="onChangeEmergencyFund($event)"
/> />
</mat-card-content> </mat-card-content>

Loading…
Cancel
Save