Browse Source

Task/improve type safety in create asset profile dialog (#6723)

* feat(client): resolve type errors in component

* fix(client): move public getter to the top

* fix(client): tighten up encapsulation and immutability

* feat(client): implement inject function
pull/6636/head^2
Kenrick Tandrian 3 days ago
committed by GitHub
parent
commit
f3438db99f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 92
      apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts
  2. 10
      apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/interfaces/interfaces.ts

92
apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts

@ -3,6 +3,7 @@ import {
ghostfolioPrefix, ghostfolioPrefix,
PROPERTY_CURRENCIES PROPERTY_CURRENCIES
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import type { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { AdminService, DataService } from '@ghostfolio/ui/services'; import { AdminService, DataService } from '@ghostfolio/ui/services';
import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete';
@ -11,6 +12,7 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
DestroyRef, DestroyRef,
inject,
OnInit OnInit
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@ -18,7 +20,6 @@ import {
AbstractControl, AbstractControl,
FormBuilder, FormBuilder,
FormControl, FormControl,
FormGroup,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
ValidationErrors, ValidationErrors,
@ -34,7 +35,10 @@ import { DataSource } from '@prisma/client';
import { isISO4217CurrencyCode } from 'class-validator'; import { isISO4217CurrencyCode } from 'class-validator';
import { switchMap } from 'rxjs'; import { switchMap } from 'rxjs';
import { CreateAssetProfileDialogMode } from './interfaces/interfaces'; import type {
CreateAssetProfileDialogMode,
CreateAssetProfileForm
} from './interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -54,32 +58,44 @@ import { CreateAssetProfileDialogMode } from './interfaces/interfaces';
templateUrl: 'create-asset-profile-dialog.html' templateUrl: 'create-asset-profile-dialog.html'
}) })
export class GfCreateAssetProfileDialogComponent implements OnInit { export class GfCreateAssetProfileDialogComponent implements OnInit {
public createAssetProfileForm: FormGroup; protected createAssetProfileForm: CreateAssetProfileForm;
public ghostfolioPrefix = `${ghostfolioPrefix}_`; protected readonly ghostfolioPrefix = `${ghostfolioPrefix}_`;
public mode: CreateAssetProfileDialogMode; protected mode: CreateAssetProfileDialogMode;
private customCurrencies: string[]; private customCurrencies: string[];
private dataSourceForExchangeRates: DataSource; private dataSourceForExchangeRates: DataSource;
public constructor( private readonly adminService = inject(AdminService);
public readonly adminService: AdminService, private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly changeDetectorRef: ChangeDetectorRef, private readonly dataService = inject(DataService);
private readonly dataService: DataService, private readonly destroyRef = inject(DestroyRef);
private readonly destroyRef: DestroyRef, private readonly dialogRef =
public readonly dialogRef: MatDialogRef<GfCreateAssetProfileDialogComponent>, inject<MatDialogRef<GfCreateAssetProfileDialogComponent>>(MatDialogRef);
public readonly formBuilder: FormBuilder private readonly formBuilder = inject(FormBuilder);
) {}
protected get showCurrencyErrorMessage() {
const addCurrencyFormControl =
this.createAssetProfileForm.controls.addCurrency;
if (addCurrencyFormControl.hasError('invalidCurrency')) {
return true;
}
return false;
}
public ngOnInit() { public ngOnInit() {
this.initialize(); this.initialize();
this.createAssetProfileForm = this.formBuilder.group( this.createAssetProfileForm = this.formBuilder.group(
{ {
addCurrency: new FormControl(null, [ addCurrency: new FormControl<string | null>(null, [
this.iso4217CurrencyCodeValidator() this.iso4217CurrencyCodeValidator()
]), ]),
addSymbol: new FormControl(null, [Validators.required]), addSymbol: new FormControl<string | null>(null, [Validators.required]),
searchSymbol: new FormControl(null, [Validators.required]) searchSymbol: new FormControl<AssetProfileIdentifier | null>(null, [
Validators.required
])
}, },
{ {
validators: this.atLeastOneValid validators: this.atLeastOneValid
@ -104,12 +120,11 @@ export class GfCreateAssetProfileDialogComponent implements OnInit {
this.dialogRef.close({ this.dialogRef.close({
addAssetProfile: true, addAssetProfile: true,
dataSource: dataSource:
this.createAssetProfileForm.get('searchSymbol').value.dataSource, this.createAssetProfileForm.controls.searchSymbol.value?.dataSource,
symbol: this.createAssetProfileForm.get('searchSymbol').value.symbol symbol: this.createAssetProfileForm.controls.searchSymbol.value?.symbol
}); });
} else if (this.mode === 'currency') { } else if (this.mode === 'currency') {
const currency = this.createAssetProfileForm.get('addCurrency') const currency = this.createAssetProfileForm.controls.addCurrency.value;
.value as string;
const currencies = Array.from( const currencies = Array.from(
new Set([...this.customCurrencies, currency]) new Set([...this.customCurrencies, currency])
@ -139,26 +154,15 @@ export class GfCreateAssetProfileDialogComponent implements OnInit {
this.dialogRef.close({ this.dialogRef.close({
addAssetProfile: true, addAssetProfile: true,
dataSource: 'MANUAL', dataSource: 'MANUAL',
symbol: `${this.ghostfolioPrefix}${this.createAssetProfileForm.get('addSymbol').value}` symbol: `${this.ghostfolioPrefix}${this.createAssetProfileForm.controls.addSymbol.value}`
}); });
} }
} }
public get showCurrencyErrorMessage() { private atLeastOneValid(control: CreateAssetProfileForm): ValidationErrors {
const addCurrencyFormControl = const addCurrencyControl = control.controls.addCurrency;
this.createAssetProfileForm.get('addCurrency'); const addSymbolControl = control.controls.addSymbol;
const searchSymbolControl = control.controls.searchSymbol;
if (addCurrencyFormControl.hasError('invalidCurrency')) {
return true;
}
return false;
}
private atLeastOneValid(control: AbstractControl): ValidationErrors {
const addCurrencyControl = control.get('addCurrency');
const addSymbolControl = control.get('addSymbol');
const searchSymbolControl = control.get('searchSymbol');
if ( if (
addCurrencyControl.valid && addCurrencyControl.valid &&
@ -170,11 +174,8 @@ export class GfCreateAssetProfileDialogComponent implements OnInit {
if ( if (
addCurrencyControl.valid || addCurrencyControl.valid ||
!addCurrencyControl ||
addSymbolControl.valid || addSymbolControl.valid ||
!addSymbolControl || searchSymbolControl.valid
searchSymbolControl.valid ||
!searchSymbolControl
) { ) {
return { atLeastOneValid: false }; return { atLeastOneValid: false };
} }
@ -189,11 +190,14 @@ export class GfCreateAssetProfileDialogComponent implements OnInit {
.subscribe(({ dataProviders, settings }) => { .subscribe(({ dataProviders, settings }) => {
this.customCurrencies = settings[PROPERTY_CURRENCIES] as string[]; this.customCurrencies = settings[PROPERTY_CURRENCIES] as string[];
const { dataSource } = dataProviders.find(({ useForExchangeRates }) => { const { dataSource } =
return useForExchangeRates; dataProviders.find(({ useForExchangeRates }) => {
}); return useForExchangeRates;
}) ?? {};
this.dataSourceForExchangeRates = dataSource; if (dataSource) {
this.dataSourceForExchangeRates = dataSource;
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });

10
apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/interfaces/interfaces.ts

@ -1,6 +1,16 @@
import type { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import type { FormControl, FormGroup } from '@angular/forms';
export interface CreateAssetProfileDialogParams { export interface CreateAssetProfileDialogParams {
deviceType: string; deviceType: string;
locale: string; locale: string;
} }
export type CreateAssetProfileDialogMode = 'auto' | 'currency' | 'manual'; export type CreateAssetProfileDialogMode = 'auto' | 'currency' | 'manual';
export type CreateAssetProfileForm = FormGroup<{
addCurrency: FormControl<string | null>;
addSymbol: FormControl<string | null>;
searchSymbol: FormControl<AssetProfileIdentifier | null>;
}>;

Loading…
Cancel
Save