Browse Source

Task/improve type safety in home watchlist and create watchlist item dialog (#6724)

* feat(client): implement CreateWatchlistItemForm

* feat(client): implement inject function

* feat(client): remove formBuilder injection

* fix(client): use AssetProfileIdentifier instead of SymbolProfile

* feat(client): implement defaultLocale to resolve type error

* fix(client): replace deprecated getDeviceInfo with deviceInfo signal

* feat(client): tighten up encapsulation and immutability
pull/6735/head
Kenrick Tandrian 4 days ago
committed by GitHub
parent
commit
34dd8be2d5
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 44
      apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts
  2. 8
      apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/interfaces/interfaces.ts
  3. 54
      apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
  4. 2
      apps/client/src/app/components/home-watchlist/home-watchlist.html

44
apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts

@ -1,9 +1,8 @@
import type { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormControl,
FormGroup,
FormsModule,
@ -15,6 +14,8 @@ import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { CreateWatchlistItemForm } from './interfaces/interfaces';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'h-100' },
@ -30,44 +31,41 @@ import { MatFormFieldModule } from '@angular/material/form-field';
styleUrls: ['./create-watchlist-item-dialog.component.scss'],
templateUrl: 'create-watchlist-item-dialog.html'
})
export class GfCreateWatchlistItemDialogComponent implements OnInit {
public createWatchlistItemForm: FormGroup;
public constructor(
public readonly dialogRef: MatDialogRef<GfCreateWatchlistItemDialogComponent>,
public readonly formBuilder: FormBuilder
) {}
public ngOnInit() {
this.createWatchlistItemForm = this.formBuilder.group(
export class GfCreateWatchlistItemDialogComponent {
protected readonly createWatchlistItemForm: CreateWatchlistItemForm =
new FormGroup(
{
searchSymbol: new FormControl(null, [Validators.required])
searchSymbol: new FormControl<AssetProfileIdentifier | null>(null, [
Validators.required
])
},
{
validators: this.validator
}
);
}
public onCancel() {
private readonly dialogRef =
inject<MatDialogRef<GfCreateWatchlistItemDialogComponent>>(MatDialogRef);
protected onCancel() {
this.dialogRef.close();
}
public onSubmit() {
protected onSubmit() {
this.dialogRef.close({
dataSource:
this.createWatchlistItemForm.get('searchSymbol').value.dataSource,
symbol: this.createWatchlistItemForm.get('searchSymbol').value.symbol
this.createWatchlistItemForm.controls.searchSymbol.value?.dataSource,
symbol: this.createWatchlistItemForm.controls.searchSymbol.value?.symbol
});
}
private validator(control: AbstractControl): ValidationErrors {
const searchSymbolControl = control.get('searchSymbol');
private validator(control: CreateWatchlistItemForm): ValidationErrors {
const searchSymbolControl = control.controls.searchSymbol;
if (
searchSymbolControl.valid &&
searchSymbolControl.value.dataSource &&
searchSymbolControl.value.symbol
searchSymbolControl.value?.dataSource &&
searchSymbolControl.value?.symbol
) {
return { incomplete: false };
}

8
apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/interfaces/interfaces.ts

@ -1,4 +1,12 @@
import type { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import type { FormControl, FormGroup } from '@angular/forms';
export interface CreateWatchlistItemDialogParams {
deviceType: string;
locale: string;
}
export type CreateWatchlistItemForm = FormGroup<{
searchSymbol: FormControl<AssetProfileIdentifier | null>;
}>;

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

@ -1,5 +1,6 @@
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { locale as defaultLocale } from '@ghostfolio/common/config';
import {
AssetProfileIdentifier,
Benchmark,
@ -14,8 +15,10 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
CUSTOM_ELEMENTS_SCHEMA,
DestroyRef,
inject,
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@ -45,26 +48,29 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/
templateUrl: './home-watchlist.html'
})
export class GfHomeWatchlistComponent implements OnInit {
public deviceType: string;
public hasImpersonationId: boolean;
public hasPermissionToCreateWatchlistItem: boolean;
public hasPermissionToDeleteWatchlistItem: boolean;
public user: User;
public watchlist: Benchmark[];
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private destroyRef: DestroyRef,
private deviceService: DeviceDetectorService,
private dialog: MatDialog,
private impersonationStorageService: ImpersonationStorageService,
private route: ActivatedRoute,
private router: Router,
private userService: UserService
) {
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
protected hasImpersonationId: boolean;
protected hasPermissionToCreateWatchlistItem: boolean;
protected hasPermissionToDeleteWatchlistItem: boolean;
protected user: User;
protected watchlist: Benchmark[];
protected readonly deviceType = computed(
() => this.deviceDetectorService.deviceInfo().deviceType
);
private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef);
private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly dialog = inject(MatDialog);
private readonly impersonationStorageService = inject(
ImpersonationStorageService
);
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly userService = inject(UserService);
public constructor() {
this.impersonationStorageService
.onChangeHasImpersonation()
.pipe(takeUntilDestroyed(this.destroyRef))
@ -110,7 +116,7 @@ export class GfHomeWatchlistComponent implements OnInit {
this.loadWatchlistData();
}
public onWatchlistItemDeleted({
protected onWatchlistItemDeleted({
dataSource,
symbol
}: AssetProfileIdentifier) {
@ -148,10 +154,10 @@ export class GfHomeWatchlistComponent implements OnInit {
>(GfCreateWatchlistItemDialogComponent, {
autoFocus: false,
data: {
deviceType: this.deviceType,
locale: this.user?.settings?.locale
deviceType: this.deviceType(),
locale: this.user?.settings?.locale ?? defaultLocale
},
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
});
dialogRef

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

@ -11,7 +11,7 @@
<div class="col-xs-12 col-md-10 offset-md-1">
<gf-benchmark
[benchmarks]="watchlist"
[deviceType]="deviceType"
[deviceType]="deviceType()"
[hasPermissionToDeleteItem]="hasPermissionToDeleteWatchlistItem"
[locale]="user?.settings?.locale || undefined"
[user]="user"

Loading…
Cancel
Save