Feature/improve allocations by etf holding (#3467)
* Improve allocations by ETF holding * Update changelog
This commit is contained in:
parent
fdcf5fd396
commit
23e4d5454d
@ -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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved the allocations by ETF holding on the allocations page (experimental)
|
||||||
|
|
||||||
## 2.86.0 - 2024-06-07
|
## 2.86.0 - 2024-06-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -204,6 +204,7 @@ export class PortfolioController {
|
|||||||
: undefined,
|
: undefined,
|
||||||
countries: hasDetails ? portfolioPosition.countries : [],
|
countries: hasDetails ? portfolioPosition.countries : [],
|
||||||
currency: hasDetails ? portfolioPosition.currency : undefined,
|
currency: hasDetails ? portfolioPosition.currency : undefined,
|
||||||
|
holdings: hasDetails ? portfolioPosition.holdings : [],
|
||||||
markets: hasDetails ? portfolioPosition.markets : undefined,
|
markets: hasDetails ? portfolioPosition.markets : undefined,
|
||||||
marketsAdvanced: hasDetails
|
marketsAdvanced: hasDetails
|
||||||
? portfolioPosition.marketsAdvanced
|
? portfolioPosition.marketsAdvanced
|
||||||
|
@ -163,6 +163,10 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface {
|
|||||||
response.holdings = [];
|
response.holdings = [];
|
||||||
|
|
||||||
for (const { label, weight } of holdings?.topHoldings ?? []) {
|
for (const { label, weight } of holdings?.topHoldings ?? []) {
|
||||||
|
if (label?.toLowerCase() === 'other') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
response.holdings.push({
|
response.holdings.push({
|
||||||
weight,
|
weight,
|
||||||
name: label
|
name: label
|
||||||
|
@ -3,7 +3,7 @@ import { AccountDetailDialogParams } from '@ghostfolio/client/components/account
|
|||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||||
import { prettifySymbol } from '@ghostfolio/common/helper';
|
import { prettifySymbol } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
Holding,
|
Holding,
|
||||||
@ -85,7 +85,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
public topHoldings: Holding[] = [];
|
public topHoldings: Holding[];
|
||||||
public topHoldingsMap: {
|
public topHoldingsMap: {
|
||||||
[name: string]: { name: string; value: number };
|
[name: string]: { name: string; value: number };
|
||||||
};
|
};
|
||||||
@ -456,21 +456,28 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
for (const holding of position.holdings) {
|
for (const holding of position.holdings) {
|
||||||
const { name, valueInBaseCurrency } = holding;
|
const { name, valueInBaseCurrency } = holding;
|
||||||
|
|
||||||
if (this.topHoldingsMap[name]?.value) {
|
if (
|
||||||
this.topHoldingsMap[name].value +=
|
!this.hasImpersonationId &&
|
||||||
valueInBaseCurrency *
|
!this.user.settings.isRestrictedView
|
||||||
(isNumber(position.valueInBaseCurrency)
|
) {
|
||||||
? position.valueInBaseCurrency
|
if (this.topHoldingsMap[name]?.value) {
|
||||||
: position.valueInPercentage);
|
this.topHoldingsMap[name].value +=
|
||||||
} else {
|
|
||||||
this.topHoldingsMap[name] = {
|
|
||||||
name,
|
|
||||||
value:
|
|
||||||
valueInBaseCurrency *
|
valueInBaseCurrency *
|
||||||
(isNumber(position.valueInBaseCurrency)
|
(isNumber(position.valueInBaseCurrency)
|
||||||
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency
|
? position.valueInBaseCurrency
|
||||||
: this.portfolioDetails.holdings[symbol].valueInPercentage)
|
: position.valueInPercentage);
|
||||||
};
|
} else {
|
||||||
|
this.topHoldingsMap[name] = {
|
||||||
|
name,
|
||||||
|
value:
|
||||||
|
valueInBaseCurrency *
|
||||||
|
(isNumber(position.valueInBaseCurrency)
|
||||||
|
? this.portfolioDetails.holdings[symbol]
|
||||||
|
.valueInBaseCurrency
|
||||||
|
: this.portfolioDetails.holdings[symbol]
|
||||||
|
.valueInPercentage)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -505,11 +512,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (this.positions[symbol].assetSubClass === 'ETF') {
|
||||||
this.positions[symbol].assetSubClass === 'ETF' &&
|
|
||||||
!this.hasImpersonationId &&
|
|
||||||
!this.user.settings.isRestrictedView
|
|
||||||
) {
|
|
||||||
this.totalValueInEtf += this.positions[symbol].value;
|
this.totalValueInEtf += this.positions[symbol].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,19 +560,21 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
this.markets[UNKNOWN_KEY].value =
|
this.markets[UNKNOWN_KEY].value =
|
||||||
this.markets[UNKNOWN_KEY].value / marketsTotal;
|
this.markets[UNKNOWN_KEY].value / marketsTotal;
|
||||||
|
|
||||||
if (!this.hasImpersonationId && !this.user.settings.isRestrictedView) {
|
this.topHoldings = Object.values(this.topHoldingsMap)
|
||||||
this.topHoldings = Object.values(this.topHoldingsMap)
|
.map(({ name, value }) => {
|
||||||
.map(({ name, value }) => {
|
return {
|
||||||
return {
|
name,
|
||||||
name,
|
allocationInPercentage:
|
||||||
allocationInPercentage:
|
this.totalValueInEtf > 0 ? value / this.totalValueInEtf : 0,
|
||||||
this.totalValueInEtf > 0 ? value / this.totalValueInEtf : 0,
|
valueInBaseCurrency: value
|
||||||
valueInBaseCurrency: value
|
};
|
||||||
};
|
})
|
||||||
})
|
.sort((a, b) => {
|
||||||
.sort((a, b) => {
|
return b.valueInBaseCurrency - a.valueInBaseCurrency;
|
||||||
return b.valueInBaseCurrency - a.valueInBaseCurrency;
|
});
|
||||||
});
|
|
||||||
|
if (this.topHoldings.length > MAX_TOP_HOLDINGS) {
|
||||||
|
this.topHoldings = this.topHoldings.slice(0, MAX_TOP_HOLDINGS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,6 +264,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<mat-card appearance="outlined" class="mb-3">
|
||||||
|
<mat-card-header class="overflow-hidden w-100">
|
||||||
|
<mat-card-title class="align-items-center d-flex text-truncate"
|
||||||
|
><span i18n>By Country</span
|
||||||
|
><gf-premium-indicator
|
||||||
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
|
class="ml-1"
|
||||||
|
/>
|
||||||
|
</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<gf-portfolio-proportion-chart
|
||||||
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
|
[keys]="['name']"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[maxItems]="10"
|
||||||
|
[positions]="countries"
|
||||||
|
/>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<mat-card appearance="outlined" class="mb-3">
|
<mat-card appearance="outlined" class="mb-3">
|
||||||
<mat-card-header class="overflow-hidden w-100">
|
<mat-card-header class="overflow-hidden w-100">
|
||||||
@ -306,57 +330,36 @@
|
|||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div
|
||||||
|
class="col-md-12"
|
||||||
|
[ngClass]="{
|
||||||
|
'd-none': !user?.settings?.isExperimentalFeatures
|
||||||
|
}"
|
||||||
|
>
|
||||||
<mat-card appearance="outlined" class="mb-3">
|
<mat-card appearance="outlined" class="mb-3">
|
||||||
<mat-card-header class="overflow-hidden w-100">
|
<mat-card-header class="overflow-hidden w-100">
|
||||||
<mat-card-title class="align-items-center d-flex text-truncate"
|
<mat-card-title class="align-items-center d-flex text-truncate"
|
||||||
><span i18n>By Country</span
|
><span i18n>By ETF Holding</span>
|
||||||
><gf-premium-indicator
|
<gf-premium-indicator
|
||||||
*ngIf="user?.subscription?.type === 'Basic'"
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
class="ml-1"
|
class="ml-1"
|
||||||
/>
|
/>
|
||||||
</mat-card-title>
|
</mat-card-title>
|
||||||
|
<mat-card-subtitle>
|
||||||
|
<ng-container i18n
|
||||||
|
>Approximation based on the Top 15 holdings per ETF</ng-container
|
||||||
|
>
|
||||||
|
</mat-card-subtitle>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-top-holdings
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
[colorScheme]="user?.settings?.colorScheme"
|
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
|
||||||
[keys]="['name']"
|
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[maxItems]="10"
|
[pageSize]="10"
|
||||||
[positions]="countries"
|
[topHoldings]="topHoldings"
|
||||||
/>
|
/>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
@if (topHoldings?.length > 0 && user?.settings?.isExperimentalFeatures) {
|
|
||||||
<div class="col-md-12">
|
|
||||||
<mat-card appearance="outlined" class="mb-3">
|
|
||||||
<mat-card-header class="overflow-hidden w-100">
|
|
||||||
<mat-card-title class="align-items-center d-flex text-truncate"
|
|
||||||
><span i18n>By ETF Holding</span>
|
|
||||||
<gf-premium-indicator
|
|
||||||
*ngIf="user?.subscription?.type === 'Basic'"
|
|
||||||
class="ml-1"
|
|
||||||
/>
|
|
||||||
</mat-card-title>
|
|
||||||
<mat-card-subtitle>
|
|
||||||
<ng-container i18n
|
|
||||||
>Approximation based on the Top 15 holdings per
|
|
||||||
ETF</ng-container
|
|
||||||
>
|
|
||||||
</mat-card-subtitle>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<gf-top-holdings
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[topHoldings]="topHoldings"
|
|
||||||
/>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,6 +89,7 @@ export const HEADER_KEY_TIMEZONE = 'Timezone';
|
|||||||
export const HEADER_KEY_TOKEN = 'Authorization';
|
export const HEADER_KEY_TOKEN = 'Authorization';
|
||||||
|
|
||||||
export const MAX_CHART_ITEMS = 365;
|
export const MAX_CHART_ITEMS = 365;
|
||||||
|
export const MAX_TOP_HOLDINGS = 50;
|
||||||
|
|
||||||
export const PROPERTY_BENCHMARKS = 'BENCHMARKS';
|
export const PROPERTY_BENCHMARKS = 'BENCHMARKS';
|
||||||
export const PROPERTY_BETTER_UPTIME_MONITOR_ID = 'BETTER_UPTIME_MONITOR_ID';
|
export const PROPERTY_BETTER_UPTIME_MONITOR_ID = 'BETTER_UPTIME_MONITOR_ID';
|
||||||
|
@ -169,7 +169,7 @@
|
|||||||
}"
|
}"
|
||||||
(click)="
|
(click)="
|
||||||
!ignoreAssetSubClasses.includes(row.assetSubClass) &&
|
!ignoreAssetSubClasses.includes(row.assetSubClass) &&
|
||||||
onOpenPositionDialog({
|
onOpenHoldingDialog({
|
||||||
dataSource: row.dataSource,
|
dataSource: row.dataSource,
|
||||||
symbol: row.symbol
|
symbol: row.symbol
|
||||||
})
|
})
|
||||||
@ -193,7 +193,7 @@
|
|||||||
|
|
||||||
@if (dataSource.data.length > pageSize && !isLoading) {
|
@if (dataSource.data.length > pageSize && !isLoading) {
|
||||||
<div class="my-3 text-center">
|
<div class="my-3 text-center">
|
||||||
<button mat-stroked-button (click)="onShowAllPositions()">
|
<button mat-stroked-button (click)="onShowAllHoldings()">
|
||||||
<ng-container i18n>Show all</ng-container>
|
<ng-container i18n>Show all</ng-container>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -102,7 +102,7 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onOpenPositionDialog({ dataSource, symbol }: UniqueAsset) {
|
public onOpenHoldingDialog({ dataSource, symbol }: UniqueAsset) {
|
||||||
if (this.hasPermissionToOpenDetails) {
|
if (this.hasPermissionToOpenDetails) {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
||||||
@ -110,7 +110,7 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onShowAllPositions() {
|
public onShowAllHoldings() {
|
||||||
this.pageSize = Number.MAX_SAFE_INTEGER;
|
this.pageSize = Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -112,7 +112,7 @@ export class GfPortfolioProportionChartComponent
|
|||||||
this.positions[symbol][this.keys[0]].toUpperCase()
|
this.positions[symbol][this.keys[0]].toUpperCase()
|
||||||
].value = chartData[
|
].value = chartData[
|
||||||
this.positions[symbol][this.keys[0]].toUpperCase()
|
this.positions[symbol][this.keys[0]].toUpperCase()
|
||||||
].value.plus(this.positions[symbol].value);
|
].value.plus(this.positions[symbol].value || 0);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
chartData[this.positions[symbol][this.keys[0]].toUpperCase()]
|
chartData[this.positions[symbol][this.keys[0]].toUpperCase()]
|
||||||
@ -124,20 +124,20 @@ export class GfPortfolioProportionChartComponent
|
|||||||
chartData[
|
chartData[
|
||||||
this.positions[symbol][this.keys[0]].toUpperCase()
|
this.positions[symbol][this.keys[0]].toUpperCase()
|
||||||
].subCategory[this.positions[symbol][this.keys[1]]].value.plus(
|
].subCategory[this.positions[symbol][this.keys[1]]].value.plus(
|
||||||
this.positions[symbol].value
|
this.positions[symbol].value || 0
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
chartData[
|
chartData[
|
||||||
this.positions[symbol][this.keys[0]].toUpperCase()
|
this.positions[symbol][this.keys[0]].toUpperCase()
|
||||||
].subCategory[
|
].subCategory[
|
||||||
this.positions[symbol][this.keys[1]] ?? UNKNOWN_KEY
|
this.positions[symbol][this.keys[1]] ?? UNKNOWN_KEY
|
||||||
] = { value: new Big(this.positions[symbol].value) };
|
] = { value: new Big(this.positions[symbol].value || 0) };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
chartData[this.positions[symbol][this.keys[0]].toUpperCase()] = {
|
chartData[this.positions[symbol][this.keys[0]].toUpperCase()] = {
|
||||||
name: this.positions[symbol][this.keys[0]],
|
name: this.positions[symbol][this.keys[0]],
|
||||||
subCategory: {},
|
subCategory: {},
|
||||||
value: new Big(this.positions[symbol].value ?? 0)
|
value: new Big(this.positions[symbol].value || 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.positions[symbol][this.keys[1]]) {
|
if (this.positions[symbol][this.keys[1]]) {
|
||||||
@ -145,7 +145,7 @@ export class GfPortfolioProportionChartComponent
|
|||||||
this.positions[symbol][this.keys[0]].toUpperCase()
|
this.positions[symbol][this.keys[0]].toUpperCase()
|
||||||
].subCategory = {
|
].subCategory = {
|
||||||
[this.positions[symbol][this.keys[1]]]: {
|
[this.positions[symbol][this.keys[1]]]: {
|
||||||
value: new Big(this.positions[symbol].value)
|
value: new Big(this.positions[symbol].value || 0)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -153,7 +153,7 @@ export class GfPortfolioProportionChartComponent
|
|||||||
} else {
|
} else {
|
||||||
if (chartData[UNKNOWN_KEY]) {
|
if (chartData[UNKNOWN_KEY]) {
|
||||||
chartData[UNKNOWN_KEY].value = chartData[UNKNOWN_KEY].value.plus(
|
chartData[UNKNOWN_KEY].value = chartData[UNKNOWN_KEY].value.plus(
|
||||||
this.positions[symbol].value
|
this.positions[symbol].value || 0
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
chartData[UNKNOWN_KEY] = {
|
chartData[UNKNOWN_KEY] = {
|
||||||
@ -161,7 +161,7 @@ export class GfPortfolioProportionChartComponent
|
|||||||
subCategory: this.keys[1]
|
subCategory: this.keys[1]
|
||||||
? { [this.keys[1]]: { value: new Big(0) } }
|
? { [this.keys[1]]: { value: new Big(0) } }
|
||||||
: undefined,
|
: undefined,
|
||||||
value: new Big(this.positions[symbol].value)
|
value: new Big(this.positions[symbol].value || 0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,7 +170,7 @@ export class GfPortfolioProportionChartComponent
|
|||||||
Object.keys(this.positions).forEach((symbol) => {
|
Object.keys(this.positions).forEach((symbol) => {
|
||||||
chartData[symbol] = {
|
chartData[symbol] = {
|
||||||
name: this.positions[symbol].name,
|
name: this.positions[symbol].name,
|
||||||
value: new Big(this.positions[symbol].value)
|
value: new Big(this.positions[symbol].value || 0)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<ng-container i18n>Name</ng-container>
|
<ng-container i18n>Name</ng-container>
|
||||||
</th>
|
</th>
|
||||||
<td *matCellDef="let element" class="px-2" mat-cell>
|
<td *matCellDef="let element" class="px-2" mat-cell>
|
||||||
{{ element?.name }}
|
{{ element?.name | titlecase }}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@ -59,3 +59,30 @@
|
|||||||
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
||||||
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
|
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<mat-paginator class="d-none" [pageSize]="pageSize" />
|
||||||
|
|
||||||
|
@if (isLoading) {
|
||||||
|
<ngx-skeleton-loader
|
||||||
|
animation="pulse"
|
||||||
|
class="px-4 py-3"
|
||||||
|
[theme]="{
|
||||||
|
height: '1.5rem',
|
||||||
|
width: '100%'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (dataSource.data.length > pageSize && !isLoading) {
|
||||||
|
<div class="my-3 text-center">
|
||||||
|
<button mat-stroked-button (click)="onShowAllHoldings()">
|
||||||
|
<ng-container i18n>Show all</ng-container>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (dataSource.data.length === 0 && !isLoading) {
|
||||||
|
<div class="p-3 text-center text-muted">
|
||||||
|
<small i18n>No data available</small>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { getLocale } from '@ghostfolio/common/helper';
|
|||||||
import { Holding } from '@ghostfolio/common/interfaces';
|
import { Holding } from '@ghostfolio/common/interfaces';
|
||||||
import { GfValueComponent } from '@ghostfolio/ui/value';
|
import { GfValueComponent } from '@ghostfolio/ui/value';
|
||||||
|
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
CUSTOM_ELEMENTS_SCHEMA,
|
CUSTOM_ELEMENTS_SCHEMA,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
@ -13,14 +14,24 @@ import {
|
|||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
||||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
import { MatSort, MatSortModule } from '@angular/material/sort';
|
||||||
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [GfValueComponent, MatButtonModule, MatSortModule, MatTableModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
GfValueComponent,
|
||||||
|
MatButtonModule,
|
||||||
|
MatPaginatorModule,
|
||||||
|
MatSortModule,
|
||||||
|
MatTableModule,
|
||||||
|
NgxSkeletonLoaderModule
|
||||||
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
selector: 'gf-top-holdings',
|
selector: 'gf-top-holdings',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@ -30,8 +41,10 @@ import { Subject } from 'rxjs';
|
|||||||
export class GfTopHoldingsComponent implements OnChanges, OnDestroy, OnInit {
|
export class GfTopHoldingsComponent implements OnChanges, OnDestroy, OnInit {
|
||||||
@Input() baseCurrency: string;
|
@Input() baseCurrency: string;
|
||||||
@Input() locale = getLocale();
|
@Input() locale = getLocale();
|
||||||
|
@Input() pageSize = Number.MAX_SAFE_INTEGER;
|
||||||
@Input() topHoldings: Holding[];
|
@Input() topHoldings: Holding[];
|
||||||
|
|
||||||
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
public dataSource: MatTableDataSource<Holding> = new MatTableDataSource();
|
public dataSource: MatTableDataSource<Holding> = new MatTableDataSource();
|
||||||
@ -40,6 +53,7 @@ export class GfTopHoldingsComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
'valueInBaseCurrency',
|
'valueInBaseCurrency',
|
||||||
'allocationInPercentage'
|
'allocationInPercentage'
|
||||||
];
|
];
|
||||||
|
public isLoading = true;
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
@ -48,14 +62,26 @@ export class GfTopHoldingsComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
public ngOnInit() {}
|
public ngOnInit() {}
|
||||||
|
|
||||||
public ngOnChanges() {
|
public ngOnChanges() {
|
||||||
if (this.topHoldings) {
|
this.isLoading = true;
|
||||||
this.dataSource = new MatTableDataSource(this.topHoldings);
|
|
||||||
|
|
||||||
this.dataSource.sort = this.sort;
|
this.dataSource = new MatTableDataSource(this.topHoldings);
|
||||||
this.dataSource.sortingDataAccessor = get;
|
this.dataSource.paginator = this.paginator;
|
||||||
|
this.dataSource.sort = this.sort;
|
||||||
|
this.dataSource.sortingDataAccessor = get;
|
||||||
|
|
||||||
|
if (this.topHoldings) {
|
||||||
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onShowAllHoldings() {
|
||||||
|
this.pageSize = Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.dataSource.paginator = this.paginator;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.unsubscribeSubject.next();
|
this.unsubscribeSubject.next();
|
||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user