Browse Source

Task/improve type safety in portfolio filter form component (#6404)

* fix(lib): resolve typescript errors in portfolio filter form

* fix(lib): type safety in template

* feat(lib): implement takeUntilDestroyed

* feat(lib): replace constructor params with injections

* feat(lib): change accounts to input signal

* feat(lib): change assetClasses to input signal

* feat(lib): change holdings to input signal

* feat(lib): change tags to input signal

* fix(lib): implement signal for disabled

* fix(lib): implement model signal for disabled

* fix(lib): reduce any types
pull/6409/head
Kenrick Tandrian 1 day ago
committed by GitHub
parent
commit
9493f79f8e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html
  2. 4
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.stories.ts
  3. 69
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts

12
libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html

@ -4,14 +4,14 @@
<mat-label i18n>Account</mat-label> <mat-label i18n>Account</mat-label>
<mat-select formControlName="account"> <mat-select formControlName="account">
<mat-option [value]="null" /> <mat-option [value]="null" />
@for (account of accounts; track account.id) { @for (account of accounts(); track account.id) {
<mat-option [value]="account.id"> <mat-option [value]="account.id">
<div class="d-flex"> <div class="d-flex">
@if (account.platform?.url) { @if (account.platform?.url) {
<gf-entity-logo <gf-entity-logo
class="mr-1" class="mr-1"
[tooltip]="account.platform?.name" [tooltip]="account.platform?.name ?? ''"
[url]="account.platform?.url" [url]="account.platform?.url ?? ''"
/> />
} }
<span>{{ account.name }}</span> <span>{{ account.name }}</span>
@ -32,7 +32,7 @@
filterForm.get('holding')?.value?.name filterForm.get('holding')?.value?.name
}}</mat-select-trigger> }}</mat-select-trigger>
<mat-option [value]="null" /> <mat-option [value]="null" />
@for (holding of holdings; track holding.name) { @for (holding of holdings(); track holding.name) {
<mat-option [value]="holding"> <mat-option [value]="holding">
<div class="line-height-1 text-truncate"> <div class="line-height-1 text-truncate">
<span <span
@ -53,7 +53,7 @@
<mat-label i18n>Tag</mat-label> <mat-label i18n>Tag</mat-label>
<mat-select formControlName="tag"> <mat-select formControlName="tag">
<mat-option [value]="null" /> <mat-option [value]="null" />
@for (tag of tags; track tag.id) { @for (tag of tags(); track tag.id) {
<mat-option [value]="tag.id">{{ tag.label }}</mat-option> <mat-option [value]="tag.id">{{ tag.label }}</mat-option>
} }
</mat-select> </mat-select>
@ -64,7 +64,7 @@
<mat-label i18n>Asset Class</mat-label> <mat-label i18n>Asset Class</mat-label>
<mat-select formControlName="assetClass"> <mat-select formControlName="assetClass">
<mat-option [value]="null" /> <mat-option [value]="null" />
@for (assetClass of assetClasses; track assetClass.id) { @for (assetClass of assetClasses(); track assetClass.id) {
<mat-option [value]="assetClass.id">{{ <mat-option [value]="assetClass.id">{{
assetClass.label assetClass.label
}}</mat-option> }}</mat-option>

4
libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.stories.ts

@ -40,7 +40,7 @@ export const Default: Story = {
{ id: 'COMMODITY', label: 'Commodity', type: 'ASSET_CLASS' }, { id: 'COMMODITY', label: 'Commodity', type: 'ASSET_CLASS' },
{ id: 'EQUITY', label: 'Equity', type: 'ASSET_CLASS' }, { id: 'EQUITY', label: 'Equity', type: 'ASSET_CLASS' },
{ id: 'FIXED_INCOME', label: 'Fixed Income', type: 'ASSET_CLASS' } { id: 'FIXED_INCOME', label: 'Fixed Income', type: 'ASSET_CLASS' }
] as any, ],
holdings: [ holdings: [
{ {
currency: 'USD', currency: 'USD',
@ -66,7 +66,7 @@ export const Default: Story = {
label: 'Retirement Fund', label: 'Retirement Fund',
type: 'TAG' type: 'TAG'
} }
] as any, ],
disabled: false disabled: false
} }
}; };

69
libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts

@ -8,12 +8,15 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
Input, DestroyRef,
OnChanges, OnChanges,
OnDestroy,
OnInit, OnInit,
forwardRef forwardRef,
inject,
input,
model
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { import {
ControlValueAccessor, ControlValueAccessor,
FormBuilder, FormBuilder,
@ -25,7 +28,6 @@ import {
} from '@angular/forms'; } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { Subject, takeUntil } from 'rxjs';
import { GfEntityLogoComponent } from '../entity-logo/entity-logo.component'; import { GfEntityLogoComponent } from '../entity-logo/entity-logo.component';
import { PortfolioFilterFormValue } from './interfaces'; import { PortfolioFilterFormValue } from './interfaces';
@ -53,33 +55,37 @@ import { PortfolioFilterFormValue } from './interfaces';
templateUrl: './portfolio-filter-form.component.html' templateUrl: './portfolio-filter-form.component.html'
}) })
export class GfPortfolioFilterFormComponent export class GfPortfolioFilterFormComponent
implements ControlValueAccessor, OnInit, OnChanges, OnDestroy implements ControlValueAccessor, OnChanges, OnInit
{ {
@Input() accounts: AccountWithPlatform[] = []; public readonly accounts = input<AccountWithPlatform[]>([]);
@Input() assetClasses: Filter[] = []; public readonly assetClasses = input<Filter[]>([]);
@Input() holdings: PortfolioPosition[] = []; public readonly disabled = model(false);
@Input() tags: Filter[] = []; public readonly holdings = input<PortfolioPosition[]>([]);
@Input() disabled = false; public readonly tags = input<Filter[]>([]);
public filterForm: FormGroup; public filterForm: FormGroup<{
account: FormControl<string | null>;
private unsubscribeSubject = new Subject<void>(); assetClass: FormControl<string | null>;
holding: FormControl<PortfolioPosition | null>;
public constructor( tag: FormControl<string | null>;
private changeDetectorRef: ChangeDetectorRef, }>;
private formBuilder: FormBuilder
) { private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly destroyRef = inject(DestroyRef);
private readonly formBuilder = inject(FormBuilder);
public constructor() {
this.filterForm = this.formBuilder.group({ this.filterForm = this.formBuilder.group({
account: new FormControl<string>(null), account: new FormControl<string | null>(null),
assetClass: new FormControl<string>(null), assetClass: new FormControl<string | null>(null),
holding: new FormControl<PortfolioPosition>(null), holding: new FormControl<PortfolioPosition | null>(null),
tag: new FormControl<string>(null) tag: new FormControl<string | null>(null)
}); });
} }
public ngOnInit() { public ngOnInit() {
this.filterForm.valueChanges this.filterForm.valueChanges
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => { .subscribe((value) => {
this.onChange(value as PortfolioFilterFormValue); this.onChange(value as PortfolioFilterFormValue);
this.onTouched(); this.onTouched();
@ -108,7 +114,7 @@ export class GfPortfolioFilterFormComponent
} }
public ngOnChanges() { public ngOnChanges() {
if (this.disabled) { if (this.disabled()) {
this.filterForm.disable({ emitEvent: false }); this.filterForm.disable({ emitEvent: false });
} else { } else {
this.filterForm.enable({ emitEvent: false }); this.filterForm.enable({ emitEvent: false });
@ -116,9 +122,9 @@ export class GfPortfolioFilterFormComponent
const tagControl = this.filterForm.get('tag'); const tagControl = this.filterForm.get('tag');
if (this.tags.length === 0) { if (this.tags().length === 0) {
tagControl?.disable({ emitEvent: false }); tagControl?.disable({ emitEvent: false });
} else if (!this.disabled) { } else if (!this.disabled()) {
tagControl?.enable({ emitEvent: false }); tagControl?.enable({ emitEvent: false });
} }
@ -134,9 +140,9 @@ export class GfPortfolioFilterFormComponent
} }
public setDisabledState(isDisabled: boolean) { public setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled; this.disabled.set(isDisabled);
if (this.disabled) { if (this.disabled()) {
this.filterForm.disable({ emitEvent: false }); this.filterForm.disable({ emitEvent: false });
} else { } else {
this.filterForm.enable({ emitEvent: false }); this.filterForm.enable({ emitEvent: false });
@ -161,11 +167,6 @@ export class GfPortfolioFilterFormComponent
} }
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
private onChange = (_value: PortfolioFilterFormValue): void => { private onChange = (_value: PortfolioFilterFormValue): void => {
// ControlValueAccessor onChange callback // ControlValueAccessor onChange callback

Loading…
Cancel
Save