Browse Source

Merge remote-tracking branch 'origin/main' into task/tags-selector-type-safety

pull/6497/head
KenTandrian 3 weeks ago
parent
commit
1b694e4d33
  1. 6
      CHANGELOG.md
  2. 52
      apps/client/src/app/pages/accounts/accounts-page.component.ts
  3. 48
      apps/client/src/app/pages/portfolio/fire/fire-page.component.ts
  4. 22
      apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts
  5. 2
      apps/client/src/styles.scss
  6. 2
      libs/common/src/lib/interfaces/lookup-item.interface.ts
  7. 94
      libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
  8. 61
      libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts
  9. 8
      package-lock.json
  10. 2
      package.json

6
CHANGELOG.md

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Upgraded `svgmap` from version `2.14.0` to `2.19.2`
## 2.249.0 - 2026-03-10
### Added

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

@ -13,7 +13,13 @@ import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table';
import { NotificationService } from '@ghostfolio/ui/notifications';
import { DataService } from '@ghostfolio/ui/services';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import {
ChangeDetectorRef,
Component,
DestroyRef,
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
@ -21,8 +27,8 @@ import { Account as AccountModel } from '@prisma/client';
import { addIcons } from 'ionicons';
import { addOutline } from 'ionicons/icons';
import { DeviceDetectorService } from 'ngx-device-detector';
import { EMPTY, Subject, Subscription } from 'rxjs';
import { catchError, takeUntil } from 'rxjs/operators';
import { EMPTY, Subscription } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { GfCreateOrUpdateAccountDialogComponent } from './create-or-update-account-dialog/create-or-update-account-dialog.component';
import { CreateOrUpdateAccountDialogParams } from './create-or-update-account-dialog/interfaces/interfaces';
@ -36,7 +42,7 @@ import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-ba
styleUrls: ['./accounts-page.scss'],
templateUrl: './accounts-page.html'
})
export class GfAccountsPageComponent implements OnDestroy, OnInit {
export class GfAccountsPageComponent implements OnInit {
public accounts: AccountModel[];
public activitiesCount = 0;
public deviceType: string;
@ -48,11 +54,10 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
public totalValueInBaseCurrency = 0;
public user: User;
private unsubscribeSubject = new Subject<void>();
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private destroyRef: DestroyRef,
private deviceService: DeviceDetectorService,
private dialog: MatDialog,
private impersonationStorageService: ImpersonationStorageService,
@ -62,7 +67,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
private userService: UserService
) {
this.route.queryParams
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((params) => {
if (params['accountId'] && params['accountDetailDialog']) {
this.openAccountDetailDialog(params['accountId']);
@ -94,13 +99,13 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
this.impersonationStorageService
.onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((impersonationId) => {
this.hasImpersonationId = !!impersonationId;
});
this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
@ -124,7 +129,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
public fetchAccounts() {
this.dataService
.fetchAccounts()
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(
({
accounts,
@ -151,11 +156,11 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
this.dataService
.deleteAccount(aId)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.userService
.get(true)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe();
this.fetchAccounts();
@ -204,18 +209,18 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
dialogRef
.afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((account: UpdateAccountDto | null) => {
if (account) {
this.reset();
this.dataService
.putAccount(account)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.userService
.get(true)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe();
this.fetchAccounts();
@ -228,11 +233,6 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private openAccountDetailDialog(aAccountId: string) {
const dialogRef = this.dialog.open<
GfAccountDetailDialogComponent,
@ -254,7 +254,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
dialogRef
.afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.fetchAccounts();
@ -284,18 +284,18 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
dialogRef
.afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((account: CreateAccountDto | null) => {
if (account) {
this.reset();
this.dataService
.postAccount(account)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.userService
.get(true)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe();
this.fetchAccounts();
@ -321,7 +321,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
dialogRef
.afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((data: any) => {
if (data) {
this.reset();
@ -343,7 +343,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
return EMPTY;
}),
takeUntil(this.unsubscribeSubject)
takeUntilDestroyed(this.destroyRef)
)
.subscribe(() => {
this.fetchAccounts();

48
apps/client/src/app/pages/portfolio/fire/fire-page.component.ts

@ -12,14 +12,18 @@ import { DataService } from '@ghostfolio/ui/services';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule, NgStyle } from '@angular/common';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import {
ChangeDetectorRef,
Component,
DestroyRef,
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormControl } from '@angular/forms';
import { Big } from 'big.js';
import { DeviceDetectorService } from 'ngx-device-detector';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
imports: [
@ -36,7 +40,7 @@ import { takeUntil } from 'rxjs/operators';
styleUrls: ['./fire-page.scss'],
templateUrl: './fire-page.html'
})
export class GfFirePageComponent implements OnDestroy, OnInit {
export class GfFirePageComponent implements OnInit {
public deviceType: string;
public fireWealth: FireWealth;
public hasImpersonationId: boolean;
@ -52,11 +56,10 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
public withdrawalRatePerYear: Big;
public withdrawalRatePerYearProjected: Big;
private unsubscribeSubject = new Subject<void>();
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private destroyRef: DestroyRef,
private deviceService: DeviceDetectorService,
private impersonationStorageService: ImpersonationStorageService,
private userService: UserService
@ -68,7 +71,7 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
this.dataService
.fetchPortfolioDetails()
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ summary }) => {
this.fireWealth = {
today: {
@ -92,19 +95,19 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
this.impersonationStorageService
.onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((impersonationId) => {
this.hasImpersonationId = !!impersonationId;
});
this.safeWithdrawalRateControl.valueChanges
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
this.onSafeWithdrawalRateChange(Number(value));
});
this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
@ -132,11 +135,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
public onAnnualInterestRateChange(annualInterestRate: number) {
this.dataService
.putUserSetting({ annualInterestRate })
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.userService
.get(true)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((user) => {
this.user = user;
@ -163,11 +166,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
retirementDate: retirementDate.toISOString(),
projectedTotalAmount: null
})
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.userService
.get(true)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((user) => {
this.user = user;
@ -179,11 +182,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
public onSafeWithdrawalRateChange(safeWithdrawalRate: number) {
this.dataService
.putUserSetting({ safeWithdrawalRate })
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.userService
.get(true)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((user) => {
this.user = user;
@ -198,11 +201,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
public onSavingsRateChange(savingsRate: number) {
this.dataService
.putUserSetting({ savingsRate })
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.userService
.get(true)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((user) => {
this.user = user;
@ -217,11 +220,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
projectedTotalAmount,
retirementDate: null
})
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.userService
.get(true)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((user) => {
this.user = user;
@ -230,11 +233,6 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private calculateWithdrawalRates() {
if (this.fireWealth && this.user?.settings?.safeWithdrawalRate) {
this.withdrawalRatePerYear = new Big(

22
apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts

@ -12,7 +12,8 @@ import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { DataService } from '@ghostfolio/ui/services';
import { NgClass } from '@angular/common';
import { ChangeDetectorRef, Component } from '@angular/core';
import { ChangeDetectorRef, Component, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import {
@ -21,7 +22,6 @@ import {
warningOutline
} from 'ionicons/icons';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject, takeUntil } from 'rxjs';
@Component({
imports: [
@ -48,11 +48,10 @@ export class GfXRayPageComponent {
public statistics: PortfolioReportResponse['xRay']['statistics'];
public user: User;
private unsubscribeSubject = new Subject<void>();
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private destroyRef: DestroyRef,
private impersonationStorageService: ImpersonationStorageService,
private userService: UserService
) {
@ -62,13 +61,13 @@ export class GfXRayPageComponent {
public ngOnInit() {
this.impersonationStorageService
.onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((impersonationId) => {
this.hasImpersonationId = !!impersonationId;
});
this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
@ -91,28 +90,23 @@ export class GfXRayPageComponent {
public onRulesUpdated(event: UpdateUserSettingDto) {
this.dataService
.putUserSetting(event)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.userService
.get(true)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe();
this.initializePortfolioReport();
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private initializePortfolioReport() {
this.isLoading = true;
this.dataService
.fetchPortfolioReport()
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ xRay: { categories, statistics } }) => {
this.categories = categories;
this.inactiveRules = this.mergeInactiveRules(categories);

2
apps/client/src/styles.scss

@ -2,7 +2,7 @@
@import './styles/table';
@import './styles/variables';
@import 'svgmap/dist/svgMap';
@import 'svgmap/style.min';
:root {
--dark-background: rgb(25, 25, 25);

2
libs/common/src/lib/interfaces/lookup-item.interface.ts

@ -7,7 +7,7 @@ export interface LookupItem {
assetSubClass: AssetSubClass;
currency: string;
dataProviderInfo: DataProviderInfo;
dataSource: DataSource;
dataSource: DataSource | null;
name: string;
symbol: string;
}

94
libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts

@ -13,12 +13,11 @@ import {
ChangeDetectionStrategy,
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnDestroy,
Output,
ViewChild
output,
viewChild
} from '@angular/core';
import { DataSource } from '@prisma/client';
import { Big } from 'big.js';
@ -81,15 +80,16 @@ export class GfPortfolioProportionChartComponent
@Input() maxItems?: number;
@Input() showLabels = false;
@Output() proportionChartClicked = new EventEmitter<AssetProfileIdentifier>();
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
public chart: Chart<'doughnut'>;
public isLoading = true;
protected readonly proportionChartClicked = output<AssetProfileIdentifier>();
private readonly OTHER_KEY = 'OTHER';
private readonly chartCanvas =
viewChild.required<ElementRef<HTMLCanvasElement>>('chartCanvas');
private colorMap: {
[symbol: string]: string;
} = {};
@ -130,45 +130,45 @@ export class GfPortfolioProportionChartComponent
};
if (this.keys.length > 0) {
const primaryKey = this.keys[0];
const secondaryKey = this.keys[1];
Object.keys(this.data).forEach((symbol) => {
if (this.data[symbol][this.keys[0]]?.toUpperCase()) {
if (chartData[this.data[symbol][this.keys[0]].toUpperCase()]) {
chartData[this.data[symbol][this.keys[0]].toUpperCase()].value =
chartData[
this.data[symbol][this.keys[0]].toUpperCase()
].value.plus(this.data[symbol].value || 0);
if (
chartData[this.data[symbol][this.keys[0]].toUpperCase()]
.subCategory[this.data[symbol][this.keys[1]]]
) {
chartData[
this.data[symbol][this.keys[0]].toUpperCase()
].subCategory[this.data[symbol][this.keys[1]]].value = chartData[
this.data[symbol][this.keys[0]].toUpperCase()
].subCategory[this.data[symbol][this.keys[1]]].value.plus(
this.data[symbol].value || 0
);
const asset = this.data[symbol];
const assetValue = asset.value || 0;
const primaryKeyValue = (asset[primaryKey] as string)?.toUpperCase();
const secondaryKeyValue = asset[secondaryKey] as string;
if (primaryKeyValue) {
if (chartData[primaryKeyValue]) {
chartData[primaryKeyValue].value =
chartData[primaryKeyValue].value.plus(assetValue);
const targetSubCategory =
chartData[primaryKeyValue].subCategory?.[secondaryKeyValue];
if (targetSubCategory) {
targetSubCategory.value =
targetSubCategory.value.plus(assetValue);
} else {
chartData[
this.data[symbol][this.keys[0]].toUpperCase()
].subCategory[this.data[symbol][this.keys[1]] ?? UNKNOWN_KEY] = {
value: new Big(this.data[symbol].value || 0)
};
if (chartData[primaryKeyValue].subCategory) {
chartData[primaryKeyValue].subCategory[
secondaryKeyValue ?? UNKNOWN_KEY
] = {
value: new Big(assetValue)
};
}
}
} else {
chartData[this.data[symbol][this.keys[0]].toUpperCase()] = {
name: this.data[symbol][this.keys[0]],
chartData[primaryKeyValue] = {
name: asset[primaryKey] as string,
subCategory: {},
value: new Big(this.data[symbol].value || 0)
value: new Big(assetValue)
};
if (this.data[symbol][this.keys[1]]) {
chartData[
this.data[symbol][this.keys[0]].toUpperCase()
].subCategory = {
[this.data[symbol][this.keys[1]]]: {
value: new Big(this.data[symbol].value || 0)
if (secondaryKeyValue) {
chartData[primaryKeyValue].subCategory = {
[secondaryKeyValue]: {
value: new Big(assetValue)
}
};
}
@ -181,10 +181,10 @@ export class GfPortfolioProportionChartComponent
} else {
chartData[UNKNOWN_KEY] = {
name: this.data[symbol].name,
subCategory: this.keys[1]
? { [this.keys[1]]: { value: new Big(0) } }
subCategory: secondaryKey
? { [secondaryKey]: { value: new Big(0) } }
: undefined,
value: new Big(this.data[symbol].value || 0)
value: new Big(assetValue)
};
}
}
@ -278,13 +278,15 @@ export class GfPortfolioProportionChartComponent
Object.keys(item.subCategory ?? {}).forEach((subCategory) => {
if (item.name === UNKNOWN_KEY) {
backgroundColorSubCategory.push(item.color);
backgroundColorSubCategory.push(item.color ?? '');
} else {
backgroundColorSubCategory.push(
Color(item.color).lighten(lightnessRatio).hex()
);
}
dataSubCategory.push(item.subCategory[subCategory].value.toNumber());
dataSubCategory.push(
item.subCategory?.[subCategory].value.toNumber() ?? 0
);
labelSubCategory.push(subCategory);
lightnessRatio += 0.1;
@ -334,7 +336,7 @@ export class GfPortfolioProportionChartComponent
labels
};
if (this.chartCanvas) {
if (this.chartCanvas()) {
if (this.chart) {
this.chart.data = data;
this.chart.options.plugins ??= {};
@ -343,7 +345,7 @@ export class GfPortfolioProportionChartComponent
this.chart.update();
} else {
this.chart = new Chart<'doughnut'>(this.chartCanvas.nativeElement, {
this.chart = new Chart<'doughnut'>(this.chartCanvas().nativeElement, {
data,
options: {
animation: false,

61
libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts

@ -8,15 +8,17 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
DoCheck,
ElementRef,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges,
ViewChild
inject,
viewChild
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
FormControl,
FormsModule,
@ -35,13 +37,12 @@ import {
import { MatInput, MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { isString } from 'lodash';
import { Subject, tap } from 'rxjs';
import { tap } from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
filter,
switchMap,
takeUntil
switchMap
} from 'rxjs/operators';
import { translate } from '../i18n';
@ -77,21 +78,21 @@ import { AbstractMatFormField } from '../shared/abstract-mat-form-field';
})
export class GfSymbolAutocompleteComponent
extends AbstractMatFormField<LookupItem>
implements DoCheck, OnChanges, OnDestroy, OnInit
implements DoCheck, OnChanges, OnInit
{
@Input() public defaultLookupItems: LookupItem[] = [];
@Input() public isLoading = false;
@ViewChild('symbolAutocomplete') public symbolAutocomplete: MatAutocomplete;
@Input() private includeIndices = false;
@ViewChild(MatInput) private input: MatInput;
public control = new FormControl();
public readonly control = new FormControl();
public lookupItems: (LookupItem & { assetSubClassString: string })[] = [];
private unsubscribeSubject = new Subject<void>();
protected readonly symbolAutocomplete =
viewChild.required<MatAutocomplete>('symbolAutocomplete');
private readonly destroyRef = inject(DestroyRef);
private readonly input = viewChild.required(MatInput);
public constructor(
public readonly _elementRef: ElementRef,
@ -105,13 +106,22 @@ export class GfSymbolAutocompleteComponent
this.controlType = 'symbol-autocomplete';
}
public get empty() {
return this.input().empty;
}
public set value(value: LookupItem) {
this.control.setValue(value);
super.value = value;
}
public ngOnInit() {
if (this.disabled) {
this.control.disable();
}
this.control.valueChanges
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
if (super.value) {
super.value.dataSource = null;
@ -136,7 +146,7 @@ export class GfSymbolAutocompleteComponent
}),
debounceTime(400),
distinctUntilChanged(),
takeUntil(this.unsubscribeSubject),
takeUntilDestroyed(this.destroyRef),
switchMap((query: string) => {
return this.dataService.fetchSymbols({
query,
@ -168,12 +178,8 @@ export class GfSymbolAutocompleteComponent
return aLookupItem?.symbol ?? '';
}
public get empty() {
return this.input?.empty;
}
public focus() {
this.input.focus();
this.input().focus();
}
public isValueInOptions(value: string) {
@ -185,7 +191,7 @@ export class GfSymbolAutocompleteComponent
public ngDoCheck() {
if (this.ngControl) {
this.validateRequired();
this.errorState = this.ngControl.invalid && this.ngControl.touched;
this.errorState = !!(this.ngControl.invalid && this.ngControl.touched);
this.stateChanges.next();
}
}
@ -197,18 +203,6 @@ export class GfSymbolAutocompleteComponent
} as LookupItem;
}
public set value(value: LookupItem) {
this.control.setValue(value);
super.value = value;
}
public ngOnDestroy() {
super.ngOnDestroy();
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private showDefaultOptions() {
this.lookupItems = this.defaultLookupItems.map((lookupItem) => {
return {
@ -224,8 +218,9 @@ export class GfSymbolAutocompleteComponent
const requiredCheck = super.required
? !super.value?.dataSource || !super.value?.symbol
: false;
if (requiredCheck) {
this.ngControl.control.setErrors({ invalidData: true });
this.ngControl.control?.setErrors({ invalidData: true });
}
}
}

8
package-lock.json

@ -90,7 +90,7 @@
"reflect-metadata": "0.2.2",
"rxjs": "7.8.1",
"stripe": "20.3.0",
"svgmap": "2.14.0",
"svgmap": "2.19.2",
"tablemark": "4.1.0",
"twitter-api-v2": "1.29.0",
"yahoo-finance2": "3.13.2",
@ -32339,9 +32339,9 @@
"license": "BSD-2-Clause"
},
"node_modules/svgmap": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/svgmap/-/svgmap-2.14.0.tgz",
"integrity": "sha512-+Vklx4DO1uv1SFq6wnJWl/dRjX4uRT9CcsIHuADxAcZ+h5X1OSyDVbNdIu837fx5TtYYuaGRhWuFCXIioN/1ww==",
"version": "2.19.2",
"resolved": "https://registry.npmjs.org/svgmap/-/svgmap-2.19.2.tgz",
"integrity": "sha512-mRqRcQiwwSTh9kTOPhjTmd3ywxA9aTfybBHGAoyuGn9CI9PnAQsuZ7H/2/VEIvgJhi1xM5IGBfk8i4/Ke4iTCQ==",
"license": "MIT",
"dependencies": {
"svg-pan-zoom": "^3.6.2"

2
package.json

@ -135,7 +135,7 @@
"reflect-metadata": "0.2.2",
"rxjs": "7.8.1",
"stripe": "20.3.0",
"svgmap": "2.14.0",
"svgmap": "2.19.2",
"tablemark": "4.1.0",
"twitter-api-v2": "1.29.0",
"yahoo-finance2": "3.13.2",

Loading…
Cancel
Save