Browse Source

Task/improve type safety in treemap chart component (#7325)

* fix(ui): resolve type errors

* feat(ui): enforce encapsulation

* feat(ui): implement viewChild signal

* feat(ui): implement output signal

* feat(ui): implement input signal
pull/7317/head^2
Kenrick Tandrian 1 day ago
committed by GitHub
parent
commit
b4a12341c5
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      apps/client/src/app/components/home-holdings/home-holdings.component.ts
  2. 74
      libs/ui/src/lib/treemap-chart/treemap-chart.component.ts

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

@ -58,7 +58,7 @@ export class GfHomeHoldingsComponent implements OnInit {
protected hasImpersonationId: boolean;
protected hasPermissionToAccessHoldingsChart: boolean;
protected hasPermissionToCreateActivity: boolean;
protected holdings: PortfolioPosition[];
protected holdings: PortfolioPosition[] | undefined;
protected holdingType: HoldingType = 'ACTIVE';
protected readonly holdingTypeOptions: ToggleOption[] = [
{ label: $localize`Active`, value: 'ACTIVE' },
@ -181,8 +181,8 @@ export class GfHomeHoldingsComponent implements OnInit {
this.viewModeFormControl.setValue(
this.deviceType === 'mobile'
? GfHomeHoldingsComponent.DEFAULT_HOLDINGS_VIEW_MODE
: this.user?.settings?.holdingsViewMode ||
GfHomeHoldingsComponent.DEFAULT_HOLDINGS_VIEW_MODE,
: (this.user?.settings?.holdingsViewMode ??
GfHomeHoldingsComponent.DEFAULT_HOLDINGS_VIEW_MODE),
{ emitEvent: false }
);
} else if (this.holdingType === 'CLOSED') {
@ -192,7 +192,7 @@ export class GfHomeHoldingsComponent implements OnInit {
);
}
this.holdings = [];
this.holdings = undefined;
this.fetchHoldings()
.pipe(takeUntilDestroyed(this.destroyRef))

74
libs/ui/src/lib/treemap-chart/treemap-chart.component.ts

@ -15,18 +15,16 @@ import {
ChangeDetectionStrategy,
Component,
ElementRef,
EventEmitter,
Input,
input,
OnChanges,
OnDestroy,
Output,
ViewChild
output,
viewChild
} from '@angular/core';
import { DataSource } from '@prisma/client';
import { Big } from 'big.js';
import type { ChartData, TooltipOptions } from 'chart.js';
import { LinearScale } from 'chart.js';
import { Chart, Tooltip } from 'chart.js';
import { Chart, LinearScale, Tooltip } from 'chart.js';
import { TreemapController, TreemapElement } from 'chartjs-chart-treemap';
import { isUUID } from 'class-validator';
import { differenceInDays, max } from 'date-fns';
@ -52,33 +50,31 @@ const { gray, green, red } = OpenColor;
export class GfTreemapChartComponent
implements AfterViewInit, OnChanges, OnDestroy
{
@Input() baseCurrency: string;
@Input() colorScheme: ColorScheme;
@Input() cursor: string;
@Input() dateRange: DateRange;
@Input() holdings: PortfolioPosition[];
@Input() locale = getLocale();
public readonly baseCurrency = input.required<string>();
public readonly colorScheme = input.required<ColorScheme>();
public readonly cursor = input.required<string>();
public readonly dateRange = input.required<DateRange>();
public readonly holdings = input<PortfolioPosition[]>();
public readonly locale = input<string>(getLocale());
@Output() treemapChartClicked = new EventEmitter<AssetProfileIdentifier>();
public readonly treemapChartClicked = output<AssetProfileIdentifier>();
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
protected isLoading = true;
public chart: Chart<'treemap'>;
public isLoading = true;
private chart: Chart<'treemap'>;
private readonly chartCanvas =
viewChild.required<ElementRef<HTMLCanvasElement>>('chartCanvas');
public constructor() {
Chart.register(LinearScale, Tooltip, TreemapController, TreemapElement);
}
public ngAfterViewInit() {
if (this.holdings) {
this.initialize();
}
this.initialize();
}
public ngOnChanges() {
if (this.holdings) {
this.initialize();
}
this.initialize();
}
public ngOnDestroy() {
@ -159,13 +155,19 @@ export class GfTreemapChartComponent
}
private initialize() {
const holdings = this.holdings();
if (!holdings) {
return;
}
this.isLoading = true;
const { endDate, startDate } = getIntervalFromDateRange({
dateRange: this.dateRange
dateRange: this.dateRange()
});
const netPerformancePercentsWithCurrencyEffect = this.holdings.map(
const netPerformancePercentsWithCurrencyEffect = holdings.map(
({ dateOfFirstActivity, netPerformancePercentWithCurrencyEffect }) => {
return getAnnualizedPerformancePercent({
daysInMarket: differenceInDays(
@ -301,12 +303,12 @@ export class GfTreemapChartComponent
},
spacing: 1,
// @ts-expect-error: should be PortfolioPosition[]
tree: this.holdings
tree: this.holdings()
}
]
};
if (this.chartCanvas) {
if (this.chartCanvas()) {
if (this.chart) {
this.chart.data = data;
this.chart.options.plugins ??= {};
@ -315,7 +317,7 @@ export class GfTreemapChartComponent
this.chart.update();
} else {
this.chart = new Chart<'treemap'>(this.chartCanvas.nativeElement, {
this.chart = new Chart<'treemap'>(this.chartCanvas().nativeElement, {
data,
options: {
animation: false,
@ -339,9 +341,9 @@ export class GfTreemapChartComponent
} catch {}
},
onHover: (event, chartElement) => {
if (this.cursor) {
if (this.cursor()) {
(event.native?.target as HTMLElement).style.cursor =
chartElement[0] ? this.cursor : 'default';
chartElement[0] ? this.cursor() : 'default';
}
},
plugins: {
@ -359,9 +361,9 @@ export class GfTreemapChartComponent
private getTooltipPluginConfiguration(): Partial<TooltipOptions<'treemap'>> {
return {
...getTooltipOptions({
colorScheme: this.colorScheme,
currency: this.baseCurrency,
locale: this.locale
colorScheme: this.colorScheme(),
currency: this.baseCurrency(),
locale: this.locale()
}),
// @ts-expect-error: no need to set all attributes in callbacks
callbacks: {
@ -381,19 +383,19 @@ export class GfTreemapChartComponent
return [
`${name ?? symbol} (${allocationInPercentage})`,
`${value?.toLocaleString(this.locale, {
`${value?.toLocaleString(this.locale(), {
maximumFractionDigits: 2,
minimumFractionDigits: 2
})} ${this.baseCurrency}`,
})} ${this.baseCurrency()}`,
'',
$localize`Change` + ' (' + $localize`Performance` + ')',
`${sign}${raw._data.netPerformanceWithCurrencyEffect.toLocaleString(
this.locale,
this.locale(),
{
maximumFractionDigits: 2,
minimumFractionDigits: 2
}
)} ${this.baseCurrency} (${netPerformanceInPercentageWithSign})`
)} ${this.baseCurrency()} (${netPerformanceInPercentageWithSign})`
];
} else {
return [

Loading…
Cancel
Save