From 6278cf6691c5f09c7a5df81d0580330064948a0e Mon Sep 17 00:00:00 2001
From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com>
Date: Wed, 8 Jul 2026 02:47:38 +0700
Subject: [PATCH] Task/improve type safety in portfolio performance component
(#7265)
Improve type safety
---
.../home-overview/home-overview.html | 1 -
.../portfolio-performance.component.html | 22 ++--
.../portfolio-performance.component.ts | 103 ++++++++++--------
3 files changed, 68 insertions(+), 58 deletions(-)
diff --git a/apps/client/src/app/components/home-overview/home-overview.html b/apps/client/src/app/components/home-overview/home-overview.html
index 8361c5b88..0ca4912b9 100644
--- a/apps/client/src/app/components/home-overview/home-overview.html
+++ b/apps/client/src/app/components/home-overview/home-overview.html
@@ -86,7 +86,6 @@
- @if (errors?.length > 0 && !isLoading) {
+ @if (errors()?.length > 0 && !isLoading()) {
}
- @if (isLoading) {
+ @if (isLoading()) {
- {{ unit }}
+ {{ unit() }}
- @if (showDetails) {
+ @if (showDetails()) {
diff --git a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts
index 56e75ec1e..a48d77a2d 100644
--- a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts
+++ b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts
@@ -13,10 +13,11 @@ import { GfValueComponent } from '@ghostfolio/ui/value';
import {
ChangeDetectionStrategy,
Component,
+ effect,
ElementRef,
- Input,
- OnChanges,
- ViewChild
+ inject,
+ input,
+ viewChild
} from '@angular/core';
import { IonIcon } from '@ionic/angular/standalone';
import { CountUp } from 'countup.js';
@@ -32,58 +33,68 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
styleUrls: ['./portfolio-performance.component.scss'],
templateUrl: './portfolio-performance.component.html'
})
-export class GfPortfolioPerformanceComponent implements OnChanges {
- @Input() deviceType: string;
- @Input() errors: ResponseError['errors'];
- @Input() isLoading: boolean;
- @Input() locale = getLocale();
- @Input() performance: PortfolioPerformance;
- @Input() precision: number;
- @Input() showDetails: boolean;
- @Input() unit: string;
+export class GfPortfolioPerformanceComponent {
+ public readonly errors = input();
+ public readonly isLoading = input();
+ public readonly locale = input(getLocale());
+ public readonly performance = input.required();
+ public readonly precision = input.required({
+ transform: (value) => {
+ return value >= 0 ? value : 2;
+ }
+ });
+ public readonly showDetails = input(false);
+ public readonly unit = input.required();
- @ViewChild('value') value: ElementRef;
+ private readonly value =
+ viewChild.required>('value');
- public constructor(private notificationService: NotificationService) {
- addIcons({ timeOutline });
- }
+ private readonly notificationService = inject(NotificationService);
- public ngOnChanges() {
- this.precision = this.precision >= 0 ? this.precision : 2;
+ public constructor() {
+ addIcons({ timeOutline });
- if (this.isLoading) {
- if (this.value?.nativeElement) {
- this.value.nativeElement.innerHTML = '';
- }
- } else {
- if (isNumber(this.performance?.currentValueInBaseCurrency)) {
- new CountUp('value', this.performance?.currentValueInBaseCurrency, {
- decimal: getNumberFormatDecimal(this.locale),
- decimalPlaces: this.precision,
- duration: 1,
- separator: getNumberFormatGroup(this.locale)
- }).start();
- } else if (this.showDetails === false) {
- new CountUp(
- 'value',
- this.performance?.netPerformancePercentageWithCurrencyEffect * 100,
- {
- decimal: getNumberFormatDecimal(this.locale),
- decimalPlaces: 2,
- duration: 1,
- separator: getNumberFormatGroup(this.locale)
- }
- ).start();
+ effect(() => {
+ if (this.isLoading()) {
+ if (this.value().nativeElement) {
+ this.value().nativeElement.innerHTML = '';
+ }
} else {
- this.value.nativeElement.innerHTML = '*****';
+ if (isNumber(this.performance().currentValueInBaseCurrency)) {
+ new CountUp('value', this.performance().currentValueInBaseCurrency, {
+ decimal: getNumberFormatDecimal(this.locale()),
+ decimalPlaces: this.precision(),
+ duration: 1,
+ separator: getNumberFormatGroup(this.locale())
+ }).start();
+ } else if (this.showDetails() === false) {
+ new CountUp(
+ 'value',
+ this.performance().netPerformancePercentageWithCurrencyEffect * 100,
+ {
+ decimal: getNumberFormatDecimal(this.locale()),
+ decimalPlaces: 2,
+ duration: 1,
+ separator: getNumberFormatGroup(this.locale())
+ }
+ ).start();
+ } else {
+ this.value().nativeElement.innerHTML = '*****';
+ }
}
- }
+ });
}
- public onShowErrors() {
- const errorMessageParts = [];
+ protected onShowErrors() {
+ const errors = this.errors();
+
+ if (!errors?.length) {
+ return;
+ }
+
+ const errorMessageParts: string[] = [];
- for (const error of this.errors) {
+ for (const error of errors) {
errorMessageParts.push(`${error.symbol} (${error.dataSource})`);
}