Browse Source

Feature/improve allocations by etf holding (#3467)

* Improve allocations by ETF holding

* Update changelog
pull/3468/head
Thomas Kaul 7 months ago
committed by GitHub
parent
commit
23e4d5454d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 1
      apps/api/src/app/portfolio/portfolio.controller.ts
  3. 4
      apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts
  4. 25
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  5. 49
      apps/client/src/app/pages/portfolio/allocations/allocations-page.html
  6. 1
      libs/common/src/lib/config.ts
  7. 4
      libs/ui/src/lib/holdings-table/holdings-table.component.html
  8. 4
      libs/ui/src/lib/holdings-table/holdings-table.component.ts
  9. 16
      libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
  10. 29
      libs/ui/src/lib/top-holdings/top-holdings.component.html
  11. 32
      libs/ui/src/lib/top-holdings/top-holdings.component.ts

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/), 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

1
apps/api/src/app/portfolio/portfolio.controller.ts

@ -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

4
apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts

@ -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

25
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -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,6 +456,10 @@ 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.hasImpersonationId &&
!this.user.settings.isRestrictedView
) {
if (this.topHoldingsMap[name]?.value) { if (this.topHoldingsMap[name]?.value) {
this.topHoldingsMap[name].value += this.topHoldingsMap[name].value +=
valueInBaseCurrency * valueInBaseCurrency *
@ -468,12 +472,15 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
value: value:
valueInBaseCurrency * valueInBaseCurrency *
(isNumber(position.valueInBaseCurrency) (isNumber(position.valueInBaseCurrency)
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency ? this.portfolioDetails.holdings[symbol]
: this.portfolioDetails.holdings[symbol].valueInPercentage) .valueInBaseCurrency
: this.portfolioDetails.holdings[symbol]
.valueInPercentage)
}; };
} }
} }
} }
}
if (position.sectors.length > 0) { if (position.sectors.length > 0) {
for (const sector of position.sectors) { for (const sector of position.sectors) {
@ -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,7 +560,6 @@ 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 {
@ -570,6 +572,9 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
.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);
} }
} }

49
apps/client/src/app/pages/portfolio/allocations/allocations-page.html

@ -267,18 +267,23 @@
<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">
<mat-card-title class="text-truncate" i18n>By Account</mat-card-title> <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-header>
<mat-card-content> <mat-card-content>
<gf-portfolio-proportion-chart <gf-portfolio-proportion-chart
cursor="pointer"
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme" [colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView" [isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['id']" [keys]="['name']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[positions]="accounts" [maxItems]="10"
(proportionChartClicked)="onAccountChartClicked($event)" [positions]="countries"
/> />
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
@ -286,22 +291,18 @@
<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">
<mat-card-title class="align-items-center d-flex text-truncate" <mat-card-title class="text-truncate" i18n>By Account</mat-card-title>
><span i18n>By ETF Provider</span
><gf-premium-indicator
*ngIf="user?.subscription?.type === 'Basic'"
class="ml-1"
/>
</mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<gf-portfolio-proportion-chart <gf-portfolio-proportion-chart
cursor="pointer"
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme" [colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView" [isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['etfProvider']" [keys]="['id']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[positions]="positions" [positions]="accounts"
(proportionChartClicked)="onAccountChartClicked($event)"
/> />
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
@ -310,7 +311,7 @@
<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 Provider</span
><gf-premium-indicator ><gf-premium-indicator
*ngIf="user?.subscription?.type === 'Basic'" *ngIf="user?.subscription?.type === 'Basic'"
class="ml-1" class="ml-1"
@ -322,16 +323,19 @@
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme" [colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView" [isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['name']" [keys]="['etfProvider']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[maxItems]="10" [positions]="positions"
[positions]="countries"
/> />
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div> </div>
@if (topHoldings?.length > 0 && user?.settings?.isExperimentalFeatures) { <div
<div class="col-md-12"> 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"
@ -343,8 +347,7 @@
</mat-card-title> </mat-card-title>
<mat-card-subtitle> <mat-card-subtitle>
<ng-container i18n <ng-container i18n
>Approximation based on the Top 15 holdings per >Approximation based on the Top 15 holdings per ETF</ng-container
ETF</ng-container
> >
</mat-card-subtitle> </mat-card-subtitle>
</mat-card-header> </mat-card-header>
@ -352,11 +355,11 @@
<gf-top-holdings <gf-top-holdings
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[pageSize]="10"
[topHoldings]="topHoldings" [topHoldings]="topHoldings"
/> />
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div> </div>
}
</div> </div>
</div> </div>

1
libs/common/src/lib/config.ts

@ -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';

4
libs/ui/src/lib/holdings-table/holdings-table.component.html

@ -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>

4
libs/ui/src/lib/holdings-table/holdings-table.component.ts

@ -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(() => {

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

@ -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)
}; };
}); });
} }

29
libs/ui/src/lib/top-holdings/top-holdings.component.html

@ -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>
}

32
libs/ui/src/lib/top-holdings/top-holdings.component.ts

@ -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,12 +62,24 @@ 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 = new MatTableDataSource(this.topHoldings);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort; this.dataSource.sort = this.sort;
this.dataSource.sortingDataAccessor = get; 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() {

Loading…
Cancel
Save