Fix rendering of currency and platform in dialogs and clean up observables (#202)

pull/205/head
Thomas 3 years ago committed by GitHub
parent d9ea255c17
commit 1135a5b335
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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
### Fixed
- Fixed rendering of currency and platform in dialogs (account and transaction)
## 1.24.0 - 07.07.2021 ## 1.24.0 - 07.07.2021
### Added ### Added

@ -52,9 +52,12 @@ export class AppComponent implements OnDestroy, OnInit {
public ngOnInit() { public ngOnInit() {
this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.dataService.fetchInfo().subscribe((info) => { this.dataService
this.info = info; .fetchInfo()
}); .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((info) => {
this.info = info;
});
this.router.events this.router.events
.pipe(filter((event) => event instanceof NavigationEnd)) .pipe(filter((event) => event instanceof NavigationEnd))

@ -51,6 +51,7 @@ export class HeaderComponent implements OnChanges {
) { ) {
this.impersonationStorageService this.impersonationStorageService
.onChangeHasImpersonation() .onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((id) => { .subscribe((id) => {
this.impersonationId = id; this.impersonationId = id;
}); });
@ -98,23 +99,26 @@ export class HeaderComponent implements OnChanges {
width: '30rem' width: '30rem'
}); });
dialogRef.afterClosed().subscribe((data) => { dialogRef
if (data?.accessToken) { .afterClosed()
this.dataService .pipe(takeUntil(this.unsubscribeSubject))
.loginAnonymous(data?.accessToken) .subscribe((data) => {
.pipe( if (data?.accessToken) {
catchError(() => { this.dataService
alert('Oops! Incorrect Security Token.'); .loginAnonymous(data?.accessToken)
.pipe(
return EMPTY; catchError(() => {
}), alert('Oops! Incorrect Security Token.');
takeUntil(this.unsubscribeSubject)
) return EMPTY;
.subscribe(({ authToken }) => { }),
this.setToken(authToken); takeUntil(this.unsubscribeSubject)
}); )
} .subscribe(({ authToken }) => {
}); this.setToken(authToken);
});
}
});
} }
public setToken(aToken: string) { public setToken(aToken: string) {
@ -125,4 +129,9 @@ export class HeaderComponent implements OnChanges {
this.router.navigate(['/']); this.router.navigate(['/']);
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

@ -7,6 +7,8 @@ import {
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { isToday, parse } from 'date-fns'; import { isToday, parse } from 'date-fns';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { LineChartItem } from '../line-chart/interfaces/line-chart.interface'; import { LineChartItem } from '../line-chart/interfaces/line-chart.interface';
import { PositionDetailDialogParams } from './interfaces/interfaces'; import { PositionDetailDialogParams } from './interfaces/interfaces';
@ -27,6 +29,8 @@ export class PerformanceChartDialog {
public historicalDataItems: LineChartItem[]; public historicalDataItems: LineChartItem[];
public title: string; public title: string;
private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
@ -35,6 +39,7 @@ export class PerformanceChartDialog {
) { ) {
this.dataService this.dataService
.fetchPositionDetail(this.benchmarkSymbol) .fetchPositionDetail(this.benchmarkSymbol)
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ currency, firstBuyDate, historicalData, marketPrice }) => { .subscribe(({ currency, firstBuyDate, historicalData, marketPrice }) => {
this.benchmarkDataItems = []; this.benchmarkDataItems = [];
this.currency = currency; this.currency = currency;
@ -84,4 +89,9 @@ export class PerformanceChartDialog {
public onClose(): void { public onClose(): void {
this.dialogRef.close(); this.dialogRef.close();
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

@ -2,11 +2,14 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
Inject Inject,
OnDestroy
} from '@angular/core'; } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { format, isSameMonth, isToday, parseISO } from 'date-fns'; import { format, isSameMonth, isToday, parseISO } from 'date-fns';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { LineChartItem } from '../../line-chart/interfaces/line-chart.interface'; import { LineChartItem } from '../../line-chart/interfaces/line-chart.interface';
import { PositionDetailDialogParams } from './interfaces/interfaces'; import { PositionDetailDialogParams } from './interfaces/interfaces';
@ -18,7 +21,7 @@ import { PositionDetailDialogParams } from './interfaces/interfaces';
templateUrl: 'position-detail-dialog.html', templateUrl: 'position-detail-dialog.html',
styleUrls: ['./position-detail-dialog.component.scss'] styleUrls: ['./position-detail-dialog.component.scss']
}) })
export class PositionDetailDialog { export class PositionDetailDialog implements OnDestroy {
public averagePrice: number; public averagePrice: number;
public benchmarkDataItems: LineChartItem[]; public benchmarkDataItems: LineChartItem[];
public currency: string; public currency: string;
@ -33,6 +36,8 @@ export class PositionDetailDialog {
public quantity: number; public quantity: number;
public transactionCount: number; public transactionCount: number;
private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
@ -41,6 +46,7 @@ export class PositionDetailDialog {
) { ) {
this.dataService this.dataService
.fetchPositionDetail(data.symbol) .fetchPositionDetail(data.symbol)
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe( .subscribe(
({ ({
averagePrice, averagePrice,
@ -135,4 +141,9 @@ export class PositionDetailDialog {
public onClose(): void { public onClose(): void {
this.dialogRef.close(); this.dialogRef.close();
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

@ -72,8 +72,11 @@ export class PositionComponent implements OnDestroy, OnInit {
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef.afterClosed().subscribe(() => { dialogRef
this.router.navigate(['.'], { relativeTo: this.route }); .afterClosed()
}); .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.router.navigate(['.'], { relativeTo: this.route });
});
} }
} }

@ -4,6 +4,7 @@ import {
EventEmitter, EventEmitter,
Input, Input,
OnChanges, OnChanges,
OnDestroy,
OnInit, OnInit,
Output, Output,
ViewChild ViewChild
@ -26,7 +27,7 @@ import { PositionDetailDialog } from '../position/position-detail-dialog/positio
templateUrl: './positions-table.component.html', templateUrl: './positions-table.component.html',
styleUrls: ['./positions-table.component.scss'] styleUrls: ['./positions-table.component.scss']
}) })
export class PositionsTableComponent implements OnChanges, OnInit { export class PositionsTableComponent implements OnChanges, OnDestroy, OnInit {
@Input() baseCurrency: string; @Input() baseCurrency: string;
@Input() deviceType: string; @Input() deviceType: string;
@Input() locale: string; @Input() locale: string;
@ -38,7 +39,8 @@ export class PositionsTableComponent implements OnChanges, OnInit {
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
public dataSource: MatTableDataSource<PortfolioPosition> = new MatTableDataSource(); public dataSource: MatTableDataSource<PortfolioPosition> =
new MatTableDataSource();
public displayedColumns = []; public displayedColumns = [];
public isLoading = true; public isLoading = true;
public pageSize = 7; public pageSize = 7;
@ -133,9 +135,12 @@ export class PositionsTableComponent implements OnChanges, OnInit {
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef.afterClosed().subscribe(() => { dialogRef
this.router.navigate(['.'], { relativeTo: this.route }); .afterClosed()
}); .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.router.navigate(['.'], { relativeTo: this.route });
});
} }
public ngOnDestroy() { public ngOnDestroy() {

@ -89,18 +89,20 @@ export class TransactionsTableComponent
} }
}); });
this.searchControl.valueChanges.subscribe((keyword) => { this.searchControl.valueChanges
if (keyword) { .pipe(takeUntil(this.unsubscribeSubject))
const filterValue = keyword.toLowerCase(); .subscribe((keyword) => {
this.filters$.next( if (keyword) {
this.allFilters.filter( const filterValue = keyword.toLowerCase();
(filter) => filter.toLowerCase().indexOf(filterValue) === 0 this.filters$.next(
) this.allFilters.filter(
); (filter) => filter.toLowerCase().indexOf(filterValue) === 0
} else { )
this.filters$.next(this.allFilters); );
} } else {
}); this.filters$.next(this.allFilters);
}
});
} }
public addKeyword({ input, value }: MatChipInputEvent): void { public addKeyword({ input, value }: MatChipInputEvent): void {
@ -223,9 +225,12 @@ export class TransactionsTableComponent
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef.afterClosed().subscribe(() => { dialogRef
this.router.navigate(['.'], { relativeTo: this.route }); .afterClosed()
}); .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.router.navigate(['.'], { relativeTo: this.route });
});
} }
public ngOnDestroy() { public ngOnDestroy() {

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { baseCurrency } from '@ghostfolio/common/config'; import { baseCurrency } from '@ghostfolio/common/config';
@ -15,7 +15,7 @@ import { environment } from '../../../environments/environment';
templateUrl: './about-page.html', templateUrl: './about-page.html',
styleUrls: ['./about-page.scss'] styleUrls: ['./about-page.scss']
}) })
export class AboutPageComponent implements OnInit { export class AboutPageComponent implements OnDestroy, OnInit {
public baseCurrency = baseCurrency; public baseCurrency = baseCurrency;
public hasPermissionForStatistics: boolean; public hasPermissionForStatistics: boolean;
public isLoggedIn: boolean; public isLoggedIn: boolean;
@ -41,6 +41,7 @@ export class AboutPageComponent implements OnInit {
public ngOnInit() { public ngOnInit() {
this.dataService this.dataService
.fetchInfo() .fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ globalPermissions, statistics }) => { .subscribe(({ globalPermissions, statistics }) => {
this.hasPermissionForStatistics = hasPermission( this.hasPermissionForStatistics = hasPermission(
globalPermissions, globalPermissions,

@ -166,6 +166,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
this.webAuthnService this.webAuthnService
.deregister() .deregister()
.pipe( .pipe(
takeUntil(this.unsubscribeSubject),
catchError(() => { catchError(() => {
this.update(); this.update();
@ -181,6 +182,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
this.webAuthnService this.webAuthnService
.register() .register()
.pipe( .pipe(
takeUntil(this.unsubscribeSubject),
catchError(() => { catchError(() => {
this.update(); this.update();

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
@ -20,7 +20,7 @@ import { CreateOrUpdateAccountDialog } from './create-or-update-account-dialog/c
templateUrl: './accounts-page.html', templateUrl: './accounts-page.html',
styleUrls: ['./accounts-page.scss'] styleUrls: ['./accounts-page.scss']
}) })
export class AccountsPageComponent implements OnInit { export class AccountsPageComponent implements OnDestroy, OnInit {
public accounts: AccountModel[]; public accounts: AccountModel[];
public deviceType: string; public deviceType: string;
public hasImpersonationId: boolean; public hasImpersonationId: boolean;
@ -71,6 +71,7 @@ export class AccountsPageComponent implements OnInit {
this.impersonationStorageService this.impersonationStorageService
.onChangeHasImpersonation() .onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((aId) => { .subscribe((aId) => {
this.hasImpersonationId = !!aId; this.hasImpersonationId = !!aId;
}); });
@ -98,23 +99,29 @@ export class AccountsPageComponent implements OnInit {
} }
public fetchAccounts() { public fetchAccounts() {
this.dataService.fetchAccounts().subscribe((response) => { this.dataService
this.accounts = response; .fetchAccounts()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.accounts = response;
if (this.accounts?.length <= 0) { if (this.accounts?.length <= 0) {
this.router.navigate([], { queryParams: { createDialog: true } }); this.router.navigate([], { queryParams: { createDialog: true } });
} }
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }
public onDeleteAccount(aId: string) { public onDeleteAccount(aId: string) {
this.dataService.deleteAccount(aId).subscribe({ this.dataService
next: () => { .deleteAccount(aId)
this.fetchAccounts(); .pipe(takeUntil(this.unsubscribeSubject))
} .subscribe({
}); next: () => {
this.fetchAccounts();
}
});
} }
public onUpdateAccount(aAccount: AccountModel) { public onUpdateAccount(aAccount: AccountModel) {
@ -146,19 +153,25 @@ export class AccountsPageComponent implements OnInit {
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef.afterClosed().subscribe((data: any) => { dialogRef
const account: UpdateAccountDto = data?.account; .afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
if (account) { .subscribe((data: any) => {
this.dataService.putAccount(account).subscribe({ const account: UpdateAccountDto = data?.account;
next: () => {
this.fetchAccounts(); if (account) {
} this.dataService
}); .putAccount(account)
} .pipe(takeUntil(this.unsubscribeSubject))
.subscribe({
next: () => {
this.fetchAccounts();
}
});
}
this.router.navigate(['.'], { relativeTo: this.route }); this.router.navigate(['.'], { relativeTo: this.route });
}); });
} }
public ngOnDestroy() { public ngOnDestroy() {
@ -181,18 +194,24 @@ export class AccountsPageComponent implements OnInit {
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef.afterClosed().subscribe((data: any) => { dialogRef
const account: CreateAccountDto = data?.account; .afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
if (account) { .subscribe((data: any) => {
this.dataService.postAccount(account).subscribe({ const account: CreateAccountDto = data?.account;
next: () => {
this.fetchAccounts(); if (account) {
} this.dataService
}); .postAccount(account)
} .pipe(takeUntil(this.unsubscribeSubject))
.subscribe({
next: () => {
this.fetchAccounts();
}
});
}
this.router.navigate(['.'], { relativeTo: this.route }); this.router.navigate(['.'], { relativeTo: this.route });
}); });
} }
} }

@ -1,7 +1,14 @@
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Inject,
OnDestroy
} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Currency } from '@prisma/client'; import { Currency } from '@prisma/client';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { DataService } from '../../../services/data.service'; import { DataService } from '../../../services/data.service';
import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces'; import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces';
@ -13,23 +20,29 @@ import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces';
styleUrls: ['./create-or-update-account-dialog.scss'], styleUrls: ['./create-or-update-account-dialog.scss'],
templateUrl: 'create-or-update-account-dialog.html' templateUrl: 'create-or-update-account-dialog.html'
}) })
export class CreateOrUpdateAccountDialog { export class CreateOrUpdateAccountDialog implements OnDestroy {
public currencies: Currency[] = []; public currencies: Currency[] = [];
public platforms: { id: string; name: string }[]; public platforms: { id: string; name: string }[];
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
public dialogRef: MatDialogRef<CreateOrUpdateAccountDialog>, public dialogRef: MatDialogRef<CreateOrUpdateAccountDialog>,
@Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateAccountDialogParams @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateAccountDialogParams
) {} ) {}
ngOnInit() { ngOnInit() {
this.dataService.fetchInfo().subscribe(({ currencies, platforms }) => { this.dataService
this.currencies = currencies; .fetchInfo()
this.platforms = platforms; .pipe(takeUntil(this.unsubscribeSubject))
}); .subscribe(({ currencies, platforms }) => {
this.currencies = currencies;
this.platforms = platforms;
this.changeDetectorRef.markForCheck();
});
} }
public onCancel(): void { public onCancel(): void {

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminService } from '@ghostfolio/client/services/admin.service';
import { CacheService } from '@ghostfolio/client/services/cache.service'; import { CacheService } from '@ghostfolio/client/services/cache.service';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
@ -19,7 +19,7 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './admin-page.html', templateUrl: './admin-page.html',
styleUrls: ['./admin-page.scss'] styleUrls: ['./admin-page.scss']
}) })
export class AdminPageComponent implements OnInit { export class AdminPageComponent implements OnDestroy, OnInit {
public dataGatheringInProgress: boolean; public dataGatheringInProgress: boolean;
public defaultDateFormat = DEFAULT_DATE_FORMAT; public defaultDateFormat = DEFAULT_DATE_FORMAT;
public exchangeRates: { label1: string; label2: string; value: number }[]; public exchangeRates: { label1: string; label2: string; value: number }[];
@ -58,11 +58,14 @@ export class AdminPageComponent implements OnInit {
} }
public onFlushCache() { public onFlushCache() {
this.cacheService.flush().subscribe(() => { this.cacheService
setTimeout(() => { .flush()
window.location.reload(); .pipe(takeUntil(this.unsubscribeSubject))
}, 300); .subscribe(() => {
}); setTimeout(() => {
window.location.reload();
}, 300);
});
} }
public onGatherMax() { public onGatherMax() {
@ -71,11 +74,14 @@ export class AdminPageComponent implements OnInit {
); );
if (confirmation === true) { if (confirmation === true) {
this.adminService.gatherMax().subscribe(() => { this.adminService
setTimeout(() => { .gatherMax()
window.location.reload(); .pipe(takeUntil(this.unsubscribeSubject))
}, 300); .subscribe(() => {
}); setTimeout(() => {
window.location.reload();
}, 300);
});
} }
} }
@ -98,11 +104,14 @@ export class AdminPageComponent implements OnInit {
const confirmation = confirm('Do you really want to delete this user?'); const confirmation = confirm('Do you really want to delete this user?');
if (confirmation) { if (confirmation) {
this.dataService.deleteUser(aId).subscribe({ this.dataService
next: () => { .deleteUser(aId)
this.fetchAdminData(); .pipe(takeUntil(this.unsubscribeSubject))
} .subscribe({
}); next: () => {
this.fetchAdminData();
}
});
} }
} }

@ -1,17 +1,21 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { import {
STAY_SIGNED_IN, STAY_SIGNED_IN,
SettingsStorageService SettingsStorageService
} from '@ghostfolio/client/services/settings-storage.service'; } from '@ghostfolio/client/services/settings-storage.service';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({ @Component({
selector: 'gf-auth-page', selector: 'gf-auth-page',
templateUrl: './auth-page.html', templateUrl: './auth-page.html',
styleUrls: ['./auth-page.scss'] styleUrls: ['./auth-page.scss']
}) })
export class AuthPageComponent implements OnInit { export class AuthPageComponent implements OnDestroy, OnInit {
private unsubscribeSubject = new Subject<void>();
/** /**
* @constructor * @constructor
*/ */
@ -26,14 +30,21 @@ export class AuthPageComponent implements OnInit {
* Initializes the controller * Initializes the controller
*/ */
public ngOnInit() { public ngOnInit() {
this.route.params.subscribe((params) => { this.route.params
const jwt = params['jwt']; .pipe(takeUntil(this.unsubscribeSubject))
this.tokenStorageService.saveToken( .subscribe((params) => {
jwt, const jwt = params['jwt'];
this.settingsStorageService.getSetting(STAY_SIGNED_IN) === 'true' this.tokenStorageService.saveToken(
); jwt,
this.settingsStorageService.getSetting(STAY_SIGNED_IN) === 'true'
);
this.router.navigate(['/']);
});
}
this.router.navigate(['/']); public ngOnDestroy() {
}); this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
} }
} }

@ -116,6 +116,7 @@ export class HomePageComponent implements OnDestroy, OnInit {
this.impersonationStorageService this.impersonationStorageService
.onChangeHasImpersonation() .onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((aId) => { .subscribe((aId) => {
this.hasImpersonationId = !!aId; this.hasImpersonationId = !!aId;
}); });
@ -148,9 +149,12 @@ export class HomePageComponent implements OnDestroy, OnInit {
width: '50rem' width: '50rem'
}); });
dialogRef.afterClosed().subscribe(() => { dialogRef
this.router.navigate(['.'], { relativeTo: this.route }); .afterClosed()
}); .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.router.navigate(['.'], { relativeTo: this.route });
});
} }
private update() { private update() {
@ -161,6 +165,7 @@ export class HomePageComponent implements OnDestroy, OnInit {
this.dataService this.dataService
.fetchChart({ range: this.dateRange }) .fetchChart({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((chartData) => { .subscribe((chartData) => {
this.historicalDataItems = chartData.map((chartDataItem) => { this.historicalDataItems = chartData.map((chartDataItem) => {
return { return {
@ -174,6 +179,7 @@ export class HomePageComponent implements OnDestroy, OnInit {
this.dataService this.dataService
.fetchPortfolioPerformance({ range: this.dateRange }) .fetchPortfolioPerformance({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => { .subscribe((response) => {
this.performance = response; this.performance = response;
this.isLoadingPerformance = false; this.isLoadingPerformance = false;
@ -181,15 +187,19 @@ export class HomePageComponent implements OnDestroy, OnInit {
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
this.dataService.fetchPortfolioOverview().subscribe((response) => { this.dataService
this.overview = response; .fetchPortfolioOverview()
this.isLoadingOverview = false; .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.overview = response;
this.isLoadingOverview = false;
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
this.dataService this.dataService
.fetchPortfolioPositions({ range: this.dateRange }) .fetchPortfolioPositions({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => { .subscribe((response) => {
this.positions = response; this.positions = response;
this.hasPositions = this.hasPositions =

@ -6,6 +6,7 @@ import { TokenStorageService } from '@ghostfolio/client/services/token-storage.s
import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({ @Component({
selector: 'gf-landing-page', selector: 'gf-landing-page',
@ -33,13 +34,16 @@ export class LandingPageComponent implements OnDestroy, OnInit {
* Initializes the controller * Initializes the controller
*/ */
public ngOnInit() { public ngOnInit() {
this.dataService.fetchInfo().subscribe(({ demoAuthToken }) => { this.dataService
this.demoAuthToken = demoAuthToken; .fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ demoAuthToken }) => {
this.demoAuthToken = demoAuthToken;
this.initializeLineChart(); this.initializeLineChart();
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }
public initializeLineChart() { public initializeLineChart() {

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { baseCurrency } from '@ghostfolio/common/config'; import { baseCurrency } from '@ghostfolio/common/config';
@ -11,7 +11,7 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './pricing-page.html', templateUrl: './pricing-page.html',
styleUrls: ['./pricing-page.scss'] styleUrls: ['./pricing-page.scss']
}) })
export class PricingPageComponent implements OnInit { export class PricingPageComponent implements OnDestroy, OnInit {
public baseCurrency = baseCurrency; public baseCurrency = baseCurrency;
public coupon: number; public coupon: number;
public isLoggedIn: boolean; public isLoggedIn: boolean;

@ -43,6 +43,7 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
public ngOnInit() { public ngOnInit() {
this.dataService this.dataService
.fetchInfo() .fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ demoAuthToken, globalPermissions }) => { .subscribe(({ demoAuthToken, globalPermissions }) => {
this.demoAuthToken = demoAuthToken; this.demoAuthToken = demoAuthToken;
this.hasPermissionForSocialLogin = hasPermission( this.hasPermissionForSocialLogin = hasPermission(
@ -76,13 +77,16 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
width: '30rem' width: '30rem'
}); });
dialogRef.afterClosed().subscribe((data) => { dialogRef
if (data?.authToken) { .afterClosed()
this.tokenStorageService.saveToken(authToken, true); .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((data) => {
if (data?.authToken) {
this.tokenStorageService.saveToken(authToken, true);
this.router.navigate(['/']); this.router.navigate(['/']);
} }
}); });
} }
public ngOnDestroy() { public ngOnDestroy() {

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { PortfolioReportRule } from '@ghostfolio/common/interfaces'; import { PortfolioReportRule } from '@ghostfolio/common/interfaces';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -9,7 +9,7 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './report-page.html', templateUrl: './report-page.html',
styleUrls: ['./report-page.scss'] styleUrls: ['./report-page.scss']
}) })
export class ReportPageComponent implements OnInit { export class ReportPageComponent implements OnDestroy, OnInit {
public accountClusterRiskRules: PortfolioReportRule[]; public accountClusterRiskRules: PortfolioReportRule[];
public currencyClusterRiskRules: PortfolioReportRule[]; public currencyClusterRiskRules: PortfolioReportRule[];
public feeRules: PortfolioReportRule[]; public feeRules: PortfolioReportRule[];

@ -2,7 +2,8 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
Inject Inject,
OnDestroy
} from '@angular/core'; } from '@angular/core';
import { FormControl, Validators } from '@angular/forms'; import { FormControl, Validators } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
@ -28,7 +29,7 @@ import { CreateOrUpdateTransactionDialogParams } from './interfaces/interfaces';
styleUrls: ['./create-or-update-transaction-dialog.scss'], styleUrls: ['./create-or-update-transaction-dialog.scss'],
templateUrl: 'create-or-update-transaction-dialog.html' templateUrl: 'create-or-update-transaction-dialog.html'
}) })
export class CreateOrUpdateTransactionDialog { export class CreateOrUpdateTransactionDialog implements OnDestroy {
public currencies: Currency[] = []; public currencies: Currency[] = [];
public currentMarketPrice = null; public currentMarketPrice = null;
public filteredLookupItems: Observable<LookupItem[]>; public filteredLookupItems: Observable<LookupItem[]>;
@ -49,10 +50,15 @@ export class CreateOrUpdateTransactionDialog {
) {} ) {}
ngOnInit() { ngOnInit() {
this.dataService.fetchInfo().subscribe(({ currencies, platforms }) => { this.dataService
this.currencies = currencies; .fetchInfo()
this.platforms = platforms; .pipe(takeUntil(this.unsubscribeSubject))
}); .subscribe(({ currencies, platforms }) => {
this.currencies = currencies;
this.platforms = platforms;
this.changeDetectorRef.markForCheck();
});
this.filteredLookupItems = this.searchSymbolCtrl.valueChanges.pipe( this.filteredLookupItems = this.searchSymbolCtrl.valueChanges.pipe(
startWith(''), startWith(''),
@ -73,6 +79,7 @@ export class CreateOrUpdateTransactionDialog {
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketPrice }) => { .subscribe(({ marketPrice }) => {
this.currentMarketPrice = marketPrice; this.currentMarketPrice = marketPrice;
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
@ -20,7 +20,7 @@ import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-
templateUrl: './transactions-page.html', templateUrl: './transactions-page.html',
styleUrls: ['./transactions-page.scss'] styleUrls: ['./transactions-page.scss']
}) })
export class TransactionsPageComponent implements OnInit { export class TransactionsPageComponent implements OnDestroy, OnInit {
public deviceType: string; public deviceType: string;
public hasImpersonationId: boolean; public hasImpersonationId: boolean;
public hasPermissionToCreateOrder: boolean; public hasPermissionToCreateOrder: boolean;
@ -71,6 +71,7 @@ export class TransactionsPageComponent implements OnInit {
this.impersonationStorageService this.impersonationStorageService
.onChangeHasImpersonation() .onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((aId) => { .subscribe((aId) => {
this.hasImpersonationId = !!aId; this.hasImpersonationId = !!aId;
}); });
@ -98,15 +99,18 @@ export class TransactionsPageComponent implements OnInit {
} }
public fetchOrders() { public fetchOrders() {
this.dataService.fetchOrders().subscribe((response) => { this.dataService
this.transactions = response; .fetchOrders()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.transactions = response;
if (this.transactions?.length <= 0) { if (this.transactions?.length <= 0) {
this.router.navigate([], { queryParams: { createDialog: true } }); this.router.navigate([], { queryParams: { createDialog: true } });
} }
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }
public onCloneTransaction(aTransaction: OrderModel) { public onCloneTransaction(aTransaction: OrderModel) {
@ -114,11 +118,14 @@ export class TransactionsPageComponent implements OnInit {
} }
public onDeleteTransaction(aId: string) { public onDeleteTransaction(aId: string) {
this.dataService.deleteOrder(aId).subscribe({ this.dataService
next: () => { .deleteOrder(aId)
this.fetchOrders(); .pipe(takeUntil(this.unsubscribeSubject))
} .subscribe({
}); next: () => {
this.fetchOrders();
}
});
} }
public onUpdateTransaction(aTransaction: OrderModel) { public onUpdateTransaction(aTransaction: OrderModel) {
@ -159,19 +166,25 @@ export class TransactionsPageComponent implements OnInit {
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef.afterClosed().subscribe((data: any) => { dialogRef
const transaction: UpdateOrderDto = data?.transaction; .afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
if (transaction) { .subscribe((data: any) => {
this.dataService.putOrder(transaction).subscribe({ const transaction: UpdateOrderDto = data?.transaction;
next: () => {
this.fetchOrders(); if (transaction) {
} this.dataService
}); .putOrder(transaction)
} .pipe(takeUntil(this.unsubscribeSubject))
.subscribe({
next: () => {
this.fetchOrders();
}
});
}
this.router.navigate(['.'], { relativeTo: this.route }); this.router.navigate(['.'], { relativeTo: this.route });
}); });
} }
public ngOnDestroy() { public ngOnDestroy() {
@ -203,18 +216,21 @@ export class TransactionsPageComponent implements OnInit {
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef.afterClosed().subscribe((data: any) => { dialogRef
const transaction: CreateOrderDto = data?.transaction; .afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
if (transaction) { .subscribe((data: any) => {
this.dataService.postOrder(transaction).subscribe({ const transaction: CreateOrderDto = data?.transaction;
next: () => {
this.fetchOrders(); if (transaction) {
} this.dataService.postOrder(transaction).subscribe({
}); next: () => {
} this.fetchOrders();
}
});
}
this.router.navigate(['.'], { relativeTo: this.route }); this.router.navigate(['.'], { relativeTo: this.route });
}); });
} }
} }

@ -1,16 +1,20 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({ @Component({
selector: 'gf-webauthn-page', selector: 'gf-webauthn-page',
templateUrl: './webauthn-page.html', templateUrl: './webauthn-page.html',
styleUrls: ['./webauthn-page.scss'] styleUrls: ['./webauthn-page.scss']
}) })
export class WebauthnPageComponent implements OnInit { export class WebauthnPageComponent implements OnDestroy, OnInit {
public hasError = false; public hasError = false;
private unsubscribeSubject = new Subject<void>();
constructor( constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private router: Router, private router: Router,
@ -23,24 +27,35 @@ export class WebauthnPageComponent implements OnInit {
} }
public deregisterDevice() { public deregisterDevice() {
this.webAuthnService.deregister().subscribe(() => { this.webAuthnService
this.router.navigate(['/']); .deregister()
}); .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.router.navigate(['/']);
});
} }
public signIn() { public signIn() {
this.hasError = false; this.hasError = false;
this.webAuthnService.login().subscribe( this.webAuthnService
({ authToken }) => { .login()
this.tokenStorageService.saveToken(authToken, false); .pipe(takeUntil(this.unsubscribeSubject))
this.router.navigate(['/']); .subscribe(
}, ({ authToken }) => {
(error) => { this.tokenStorageService.saveToken(authToken, false);
console.error(error); this.router.navigate(['/']);
this.hasError = true; },
this.changeDetectorRef.markForCheck(); (error) => {
} console.error(error);
); this.hasError = true;
this.changeDetectorRef.markForCheck();
}
);
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
} }
} }

@ -61,6 +61,7 @@ export class ZenPageComponent implements OnDestroy, OnInit {
this.impersonationStorageService this.impersonationStorageService
.onChangeHasImpersonation() .onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((aId) => { .subscribe((aId) => {
this.hasImpersonationId = !!aId; this.hasImpersonationId = !!aId;
}); });
@ -78,6 +79,7 @@ export class ZenPageComponent implements OnDestroy, OnInit {
this.dataService this.dataService
.fetchChart({ range: this.dateRange }) .fetchChart({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((chartData) => { .subscribe((chartData) => {
this.historicalDataItems = chartData.map((chartDataItem) => { this.historicalDataItems = chartData.map((chartDataItem) => {
return { return {
@ -91,6 +93,7 @@ export class ZenPageComponent implements OnDestroy, OnInit {
this.dataService this.dataService
.fetchPortfolioPerformance({ range: this.dateRange }) .fetchPortfolioPerformance({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => { .subscribe((response) => {
this.performance = response; this.performance = response;
this.isLoadingPerformance = false; this.isLoadingPerformance = false;

Loading…
Cancel
Save