Browse Source

Task/improve Angular template type safety in client (#7763)

* feat(client): resolve type errors

* feat(client): enable strict safe navigation types

* feat(client): resolve type errors

* feat(ui): resolve type errors

* feat(client): default to default locale
pull/4198/merge
Kenrick Tandrian 1 week ago
committed by GitHub
parent
commit
fd1908f9a0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
  2. 4
      apps/client/src/app/components/admin-settings/admin-settings.component.html
  3. 3
      apps/client/src/app/components/admin-settings/admin-settings.component.ts
  4. 3
      apps/client/src/app/components/home-holdings/home-holdings.component.ts
  5. 6
      apps/client/src/app/components/home-holdings/home-holdings.html
  6. 2
      apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
  7. 2
      apps/client/src/app/components/home-watchlist/home-watchlist.html
  8. 3
      apps/client/src/app/components/markets/markets.component.ts
  9. 2
      apps/client/src/app/components/markets/markets.html
  10. 2
      apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html
  11. 8
      apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
  12. 3
      apps/client/src/app/pages/accounts/accounts-page.component.ts
  13. 4
      apps/client/src/app/pages/accounts/accounts-page.html
  14. 2
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  15. 2
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html
  16. 8
      apps/client/src/app/pages/portfolio/allocations/allocations-page.html
  17. 2
      apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html
  18. 2
      apps/client/tsconfig.json
  19. 2
      libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.html
  20. 8
      libs/ui/src/lib/line-chart/line-chart.component.ts

8
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html

@ -71,10 +71,10 @@
type="button" type="button"
[disabled]=" [disabled]="
!canDeleteAssetProfile({ !canDeleteAssetProfile({
activitiesCount: assetProfile?.activitiesCount, activitiesCount: assetProfile?.activitiesCount ?? 0,
isBenchmark: isBenchmark, isBenchmark: isBenchmark,
symbol: data.symbol, symbol: data.symbol,
watchedByCount: assetProfile?.watchedByCount watchedByCount: assetProfile?.watchedByCount ?? 0
}) })
" "
(click)=" (click)="
@ -272,8 +272,8 @@
> >
</div> </div>
@if ( @if (
assetProfile?.countries?.length > 0 || (assetProfile?.countries?.length ?? 0) > 0 ||
assetProfile?.sectors?.length > 0 (assetProfile?.sectors?.length ?? 0) > 0
) { ) {
@if ( @if (
assetProfile?.countries?.length === 1 && assetProfile?.countries?.length === 1 &&

4
apps/client/src/app/components/admin-settings/admin-settings.component.html

@ -230,13 +230,13 @@
<div class="mb-5 row"> <div class="mb-5 row">
<div class="col"> <div class="col">
<h2 class="text-center" i18n>Platforms</h2> <h2 class="text-center" i18n>Platforms</h2>
<gf-admin-platform [locale]="user?.settings?.locale" /> <gf-admin-platform [locale]="user?.settings?.locale ?? DEFAULT_LOCALE" />
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<h2 class="text-center" i18n>Tags</h2> <h2 class="text-center" i18n>Tags</h2>
<gf-admin-tag [locale]="user?.settings?.locale" /> <gf-admin-tag [locale]="user?.settings?.locale ?? DEFAULT_LOCALE" />
</div> </div>
</div> </div>
</div> </div>

3
apps/client/src/app/components/admin-settings/admin-settings.component.ts

@ -3,6 +3,7 @@ import { GfAdminTagComponent } from '@ghostfolio/client/components/admin-tag/adm
import { GfDataProviderStatusComponent } from '@ghostfolio/client/components/data-provider-status/data-provider-status.component'; import { GfDataProviderStatusComponent } from '@ghostfolio/client/components/data-provider-status/data-provider-status.component';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { import {
DEFAULT_LOCALE,
E_MAIL_LINE_BREAK, E_MAIL_LINE_BREAK,
PROPERTY_API_KEY_GHOSTFOLIO PROPERTY_API_KEY_GHOSTFOLIO
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
@ -98,6 +99,8 @@ export class GfAdminSettingsComponent implements OnInit {
public pricingUrl: string; public pricingUrl: string;
public user: User; public user: User;
protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE;
public constructor( public constructor(
private adminService: AdminService, private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,

3
apps/client/src/app/components/home-holdings/home-holdings.component.ts

@ -1,4 +1,5 @@
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
PortfolioPosition, PortfolioPosition,
@ -50,6 +51,8 @@ import { DeviceDetectorService } from 'ngx-device-detector';
export class GfHomeHoldingsComponent implements OnInit { export class GfHomeHoldingsComponent implements OnInit {
public static DEFAULT_HOLDINGS_VIEW_MODE: HoldingsViewMode = 'TABLE'; public static DEFAULT_HOLDINGS_VIEW_MODE: HoldingsViewMode = 'TABLE';
protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE;
protected deviceType: string; protected deviceType: string;
protected hasPermissionToAccessHoldingsChart: boolean; protected hasPermissionToAccessHoldingsChart: boolean;
protected hasPermissionToCreateActivity: boolean; protected hasPermissionToCreateActivity: boolean;

6
apps/client/src/app/components/home-holdings/home-holdings.html

@ -37,11 +37,11 @@
} }
<div [class.d-none]="holdingsViewMode !== 'TABLE'"> <div [class.d-none]="holdingsViewMode !== 'TABLE'">
<gf-holdings-table <gf-holdings-table
[holdings]="holdings" [holdings]="holdings ?? []"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale ?? DEFAULT_LOCALE"
(holdingClicked)="onHoldingClicked($event)" (holdingClicked)="onHoldingClicked($event)"
/> />
@if (hasPermissionToCreateActivity && holdings?.length > 0) { @if (hasPermissionToCreateActivity && (holdings?.length ?? 0) > 0) {
<div class="text-center"> <div class="text-center">
<a <a
class="mt-3" class="mt-3"

2
apps/client/src/app/components/home-watchlist/home-watchlist.component.ts

@ -44,6 +44,8 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/
templateUrl: './home-watchlist.html' templateUrl: './home-watchlist.html'
}) })
export class GfHomeWatchlistComponent implements OnInit { export class GfHomeWatchlistComponent implements OnInit {
protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE;
protected hasPermissionToCreateWatchlistItem: boolean; protected hasPermissionToCreateWatchlistItem: boolean;
protected hasPermissionToDeleteWatchlistItem: boolean; protected hasPermissionToDeleteWatchlistItem: boolean;
protected user: User; protected user: User;

2
apps/client/src/app/components/home-watchlist/home-watchlist.html

@ -13,7 +13,7 @@
[benchmarks]="watchlist" [benchmarks]="watchlist"
[deviceType]="deviceType()" [deviceType]="deviceType()"
[hasPermissionToDeleteItem]="hasPermissionToDeleteWatchlistItem" [hasPermissionToDeleteItem]="hasPermissionToDeleteWatchlistItem"
[locale]="user?.settings?.locale || undefined" [locale]="user?.settings?.locale ?? DEFAULT_LOCALE"
[showIcon]="true" [showIcon]="true"
[user]="user" [user]="user"
(itemDeleted)="onWatchlistItemDeleted($event)" (itemDeleted)="onWatchlistItemDeleted($event)"

3
apps/client/src/app/components/markets/markets.component.ts

@ -1,4 +1,5 @@
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { resetHours } from '@ghostfolio/common/helper'; import { resetHours } from '@ghostfolio/common/helper';
import { import {
Benchmark, Benchmark,
@ -43,6 +44,8 @@ import { DeviceDetectorService } from 'ngx-device-detector';
templateUrl: './markets.html' templateUrl: './markets.html'
}) })
export class GfMarketsComponent implements OnInit { export class GfMarketsComponent implements OnInit {
protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE;
protected readonly benchmarks = signal<Benchmark[] | undefined>(undefined); protected readonly benchmarks = signal<Benchmark[] | undefined>(undefined);
protected readonly deviceType = computed( protected readonly deviceType = computed(

2
apps/client/src/app/components/markets/markets.html

@ -52,7 +52,7 @@
<gf-benchmark <gf-benchmark
[benchmarks]="benchmarks()" [benchmarks]="benchmarks()"
[deviceType]="deviceType()" [deviceType]="deviceType()"
[locale]="user?.settings?.locale || undefined" [locale]="user?.settings?.locale ?? DEFAULT_LOCALE"
[showSymbol]="false" [showSymbol]="false"
[user]="user" [user]="user"
/> />

2
apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html

@ -1,7 +1,7 @@
<div class="container p-0"> <div class="container p-0">
<div class="no-gutters row"> <div class="no-gutters row">
<div class="status-container text-muted text-right"> <div class="status-container text-muted text-right">
@if (errors()?.length > 0 && !isLoading()) { @if ((errors()?.length ?? 0) > 0 && !isLoading()) {
<ion-icon <ion-icon
i18n-title i18n-title
name="time-outline" name="time-outline"

8
apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html

@ -183,7 +183,7 @@
<ng-container i18n>Holdings</ng-container> <ng-container i18n>Holdings</ng-container>
@if ( @if (
!hasImpersonationId && !hasImpersonationId &&
summary?.totalValueInBaseCurrency > 0 && (summary?.totalValueInBaseCurrency ?? 0) > 0 &&
user?.settings?.isExperimentalFeatures user?.settings?.isExperimentalFeatures
) { ) {
<gf-value <gf-value
@ -261,7 +261,7 @@
<ng-container i18n>Cash</ng-container> <ng-container i18n>Cash</ng-container>
@if ( @if (
!hasImpersonationId && !hasImpersonationId &&
summary?.totalValueInBaseCurrency > 0 && (summary?.totalValueInBaseCurrency ?? 0) > 0 &&
user?.settings?.isExperimentalFeatures user?.settings?.isExperimentalFeatures
) { ) {
<gf-value <gf-value
@ -324,7 +324,7 @@
<ng-container i18n>Excluded from Analysis</ng-container> <ng-container i18n>Excluded from Analysis</ng-container>
@if ( @if (
!hasImpersonationId && !hasImpersonationId &&
summary?.totalValueInBaseCurrency > 0 && (summary?.totalValueInBaseCurrency ?? 0) > 0 &&
user?.settings?.isExperimentalFeatures user?.settings?.isExperimentalFeatures
) { ) {
<gf-value <gf-value
@ -412,7 +412,7 @@
<ng-container i18n>Emergency Fund</ng-container> <ng-container i18n>Emergency Fund</ng-container>
@if ( @if (
!hasImpersonationId && !hasImpersonationId &&
summary?.totalValueInBaseCurrency > 0 && (summary?.totalValueInBaseCurrency ?? 0) > 0 &&
user?.settings?.isExperimentalFeatures user?.settings?.isExperimentalFeatures
) { ) {
<gf-value <gf-value

3
apps/client/src/app/pages/accounts/accounts-page.component.ts

@ -1,5 +1,6 @@
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 { DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { TransferBalanceDto } from '@ghostfolio/common/dtos'; import { TransferBalanceDto } from '@ghostfolio/common/dtos';
import { AccountsResponse, User } from '@ghostfolio/common/interfaces'; import { AccountsResponse, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
@ -39,6 +40,8 @@ import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-ba
templateUrl: './accounts-page.html' templateUrl: './accounts-page.html'
}) })
export class GfAccountsPageComponent implements OnInit { export class GfAccountsPageComponent implements OnInit {
protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE;
protected accounts: AccountWithValue[] | undefined; protected accounts: AccountWithValue[] | undefined;
protected activitiesCount = 0; protected activitiesCount = 0;
protected hasPermissionToCreateAccount: boolean; protected hasPermissionToCreateAccount: boolean;

4
apps/client/src/app/pages/accounts/accounts-page.html

@ -3,12 +3,12 @@
<div class="col"> <div class="col">
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>Accounts</h1> <h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>Accounts</h1>
<gf-accounts-table <gf-accounts-table
[accounts]="accounts" [accounts]="accounts ?? []"
[activitiesCount]="activitiesCount" [activitiesCount]="activitiesCount"
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[hasPermissionToDeleteAccount]="hasPermissionToDeleteAccount" [hasPermissionToDeleteAccount]="hasPermissionToDeleteAccount"
[hasPermissionToUpdateAccount]="hasPermissionToUpdateAccount" [hasPermissionToUpdateAccount]="hasPermissionToUpdateAccount"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale ?? DEFAULT_LOCALE"
[showActions]=" [showActions]="
(hasPermissionToDeleteAccount || hasPermissionToUpdateAccount) && (hasPermissionToDeleteAccount || hasPermissionToUpdateAccount) &&
!user.settings.isRestrictedView !user.settings.isRestrictedView

2
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -79,6 +79,8 @@ import { ActivityType } from './types/activity-type.type';
templateUrl: 'create-or-update-activity-dialog.html' templateUrl: 'create-or-update-activity-dialog.html'
}) })
export class GfCreateOrUpdateActivityDialogComponent { export class GfCreateOrUpdateActivityDialogComponent {
protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE;
protected activityForm: FormGroup; protected activityForm: FormGroup;
protected readonly assetClassOptions: AssetClassSelectorOption[] = protected readonly assetClassOptions: AssetClassSelectorOption[] =

2
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html

@ -309,7 +309,7 @@
<gf-value <gf-value
class="flex-grow-1" class="flex-grow-1"
[isCurrency]="true" [isCurrency]="true"
[locale]="data.user?.settings?.locale" [locale]="data.user?.settings?.locale ?? DEFAULT_LOCALE"
[unit]=" [unit]="
activityForm.get('currencyOfUnitPrice')?.value ?? activityForm.get('currencyOfUnitPrice')?.value ??
data.user?.settings?.baseCurrency data.user?.settings?.baseCurrency

8
apps/client/src/app/pages/portfolio/allocations/allocations-page.html

@ -23,9 +23,11 @@
<mat-progress-bar <mat-progress-bar
mode="determinate" mode="determinate"
[title]="`${( [title]="`${(
portfolioDetails?.summary?.filteredValueInPercentage * 100 (portfolioDetails?.summary?.filteredValueInPercentage ?? 0) * 100
).toFixed(2)}%`" ).toFixed(2)}%`"
[value]="portfolioDetails?.summary?.filteredValueInPercentage * 100" [value]="
(portfolioDetails?.summary?.filteredValueInPercentage ?? 0) * 100
"
/> />
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
@ -236,7 +238,7 @@
>Other Markets</gf-value >Other Markets</gf-value
> >
</div> </div>
@if (markets?.[UNKNOWN_KEY]?.valueInPercentage > 0) { @if ((markets?.[UNKNOWN_KEY]?.valueInPercentage ?? 0) > 0) {
<div class="col-xs-12 col-md my-2"> <div class="col-xs-12 col-md my-2">
<gf-value <gf-value
i18n i18n

2
apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html

@ -81,7 +81,7 @@
<button <button
color="secondary" color="secondary"
mat-flat-button mat-flat-button
[cdkCopyToClipboard]="accessToken" [cdkCopyToClipboard]="accessToken ?? ''"
(click)="enableCreateAccountButton()" (click)="enableCreateAccountButton()"
> >
<ion-icon class="mr-1" name="copy-outline" /> <ion-icon class="mr-1" name="copy-outline" />

2
apps/client/tsconfig.json

@ -25,7 +25,7 @@
"strictLiteralTypes": true, "strictLiteralTypes": true,
"strictNullInputTypes": true, "strictNullInputTypes": true,
"strictOutputEventTypes": true, "strictOutputEventTypes": true,
"strictSafeNavigationTypes": false, "strictSafeNavigationTypes": true,
// TODO: Enable stricter rules for this project // TODO: Enable stricter rules for this project
"strictTemplates": false "strictTemplates": false
}, },

2
libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.html

@ -1,7 +1,7 @@
<gf-dialog-header <gf-dialog-header
position="center" position="center"
[deviceType]="data.deviceType" [deviceType]="data.deviceType"
[title]="assetProfile?.name ?? assetProfile?.symbol" [title]="assetProfile.name ?? assetProfile.symbol ?? ''"
(closeButtonClicked)="onClose()" (closeButtonClicked)="onClose()"
/> />

8
libs/ui/src/lib/line-chart/line-chart.component.ts

@ -4,7 +4,11 @@ import {
getValueAxisOptions, getValueAxisOptions,
getVerticalHoverLinePlugin getVerticalHoverLinePlugin
} from '@ghostfolio/common/chart-helper'; } from '@ghostfolio/common/chart-helper';
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config'; import {
DEFAULT_COLOR_SCHEME,
primaryColorRgb,
secondaryColorRgb
} from '@ghostfolio/common/config';
import { getBackgroundColor, getLocale } from '@ghostfolio/common/helper'; import { getBackgroundColor, getLocale } from '@ghostfolio/common/helper';
import { LineChartItem } from '@ghostfolio/common/interfaces'; import { LineChartItem } from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types'; import { ColorScheme } from '@ghostfolio/common/types';
@ -52,7 +56,7 @@ export class GfLineChartComponent
{ {
@Input() benchmarkDataItems: LineChartItem[] = []; @Input() benchmarkDataItems: LineChartItem[] = [];
@Input() benchmarkLabel = ''; @Input() benchmarkLabel = '';
@Input() colorScheme: ColorScheme; @Input() colorScheme: ColorScheme = DEFAULT_COLOR_SCHEME;
@Input() currency: string; @Input() currency: string;
@Input() historicalDataItems: LineChartItem[]; @Input() historicalDataItems: LineChartItem[];
@Input() isAnimated = false; @Input() isAnimated = false;

Loading…
Cancel
Save