diff --git a/src/app/core-ui/main/main-view.component.ts b/src/app/core-ui/main/main-view.component.ts index 5e08af4d6f..2066c5136b 100644 --- a/src/app/core-ui/main/main-view.component.ts +++ b/src/app/core-ui/main/main-view.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { Router, ActivatedRoute, NavigationEnd } from '@angular/router'; import { Log } from 'ng2-logger'; import { MatDialog } from '@angular/material'; @@ -9,7 +9,6 @@ import { environment } from '../../../environments/environment'; import { RpcService } from '../../core/core.module'; import { ModalsService } from '../../modals/modals.module'; import { TransactionService } from '../../wallet/wallet/shared/transaction.service'; -import { DaemonConnectionComponent } from '../../modals/shared/daemon-connection/daemon-connection.component'; /* * The MainView is basically: * sidebar + router-outlet. @@ -21,7 +20,7 @@ import { DaemonConnectionComponent } from '../../modals/shared/daemon-connection templateUrl: './main-view.component.html', styleUrls: ['./main-view.component.scss'] }) -export class MainViewComponent implements OnInit { +export class MainViewComponent implements OnInit, OnDestroy { log: any = Log.create('main-view.component'); /* UI States */ @@ -38,6 +37,7 @@ export class MainViewComponent implements OnInit { unSubscribeTimer: any; time: string = '5:00'; public unlocked_until: number = 0; + private destroyed: boolean = false; constructor( private _router: Router, private _route: ActivatedRoute, @@ -77,10 +77,12 @@ export class MainViewComponent implements OnInit { // Updates the error box in the sidenav if wallet is not initialized. this._rpc.state.observe('ui:walletInitialized') - .subscribe(status => this.walletInitialized = status); + .takeWhile(() => !this.destroyed) + .subscribe(status => this.walletInitialized = status); this._rpc.state.observe('unlocked_until') + .takeWhile(() => !this.destroyed) .subscribe(status => { this.unlocked_until = status; if (this.unlocked_until > 0) { @@ -95,10 +97,13 @@ export class MainViewComponent implements OnInit { /* versions */ // Obtains the current daemon version this._rpc.state.observe('subversion') - .subscribe( - subversion => this.daemonVersion = subversion.match(/\d+\.\d+.\d+.\d+/)[0]); + .takeWhile(() => !this.destroyed) + .subscribe(subversion => this.daemonVersion = subversion.match(/\d+\.\d+.\d+.\d+/)[0]); } + ngOnDestroy() { + this.destroyed = true; + } /** Open createwallet modal when clicking on error in sidenav */ createWallet() { this._modals.open('createWallet', {forceOpen: true}); diff --git a/src/app/core-ui/main/status/status.component.ts b/src/app/core-ui/main/status/status.component.ts index ae80693c2d..1959308476 100644 --- a/src/app/core-ui/main/status/status.component.ts +++ b/src/app/core-ui/main/status/status.component.ts @@ -1,5 +1,5 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material'; import { Subscription } from 'rxjs/Subscription'; import { Log } from 'ng2-logger'; @@ -14,12 +14,13 @@ import { ConsoleModalComponent } from './modal/help-modal/console-modal.componen templateUrl: './status.component.html', styleUrls: ['./status.component.scss'] }) -export class StatusComponent implements OnInit { +export class StatusComponent implements OnInit, OnDestroy { peerListCount: number = 0; public coldStakingStatus: boolean; public encryptionStatus: string = 'Locked'; private _sub: Subscription; + private destroyed: boolean = false; private log: any = Log.create('status.component'); @@ -31,16 +32,23 @@ export class StatusComponent implements OnInit { ngOnInit() { this._rpc.state.observe('connections') + .takeWhile(() => !this.destroyed) .subscribe(connections => this.peerListCount = connections); this._rpc.state.observe('encryptionstatus') + .takeWhile(() => !this.destroyed) .subscribe(status => this.encryptionStatus = status); this._rpc.state.observe('ui:coldstaking') + .takeWhile(() => !this.destroyed) .subscribe(status => this.coldStakingStatus = status); /* Bug: If you remove this line, then the state of 'txcount' doesn't update in the Transaction.service */ - this._rpc.state.observe('txcount').subscribe(txcount => { }); + this._rpc.state.observe('txcount').takeWhile(() => !this.destroyed).subscribe(txcount => { }); + } + + ngOnDestroy() { + this.destroyed = true; } getIconNumber(): number { diff --git a/src/app/core/rpc/rpc-state/rpc-state.class.ts b/src/app/core/rpc/rpc-state/rpc-state.class.ts index f2af003363..b3108e0a98 100644 --- a/src/app/core/rpc/rpc-state/rpc-state.class.ts +++ b/src/app/core/rpc/rpc-state/rpc-state.class.ts @@ -1,10 +1,11 @@ import { Log } from 'ng2-logger'; import { RpcService } from '../rpc.service'; +import { OnDestroy } from '@angular/core'; - -export class RpcStateClass { +export class RpcStateClass implements OnDestroy { private log: any = Log.create('rpc-state.class'); + private destroyed: boolean = false; constructor(private _rpc: RpcService) { // Start polling... @@ -20,19 +21,25 @@ export class RpcStateClass { this.initWalletState(); } + ngOnDestroy() { + this.destroyed = true; + } + private lastBlockTimeState() { let _checkLastBlock = false; - this._rpc.state.observe('mediantime').subscribe(mediantime => { - const now = new Date().getTime() - (4 * 60 * 1000); - const lastblocktime = new Date(mediantime * 1000); - if (!_checkLastBlock && now > lastblocktime.getTime()) { - _checkLastBlock = true; - setTimeout(() => { - _checkLastBlock = false; - this._rpc.stateCall('getblockchaininfo'); - }, 100); - } - }); + this._rpc.state.observe('mediantime') + .takeWhile(() => !this.destroyed) + .subscribe(mediantime => { + const now = new Date().getTime() - (4 * 60 * 1000); + const lastblocktime = new Date(mediantime * 1000); + if (!_checkLastBlock && now > lastblocktime.getTime()) { + _checkLastBlock = true; + setTimeout(() => { + _checkLastBlock = false; + this._rpc.stateCall('getblockchaininfo'); + }, 100); + } + }); } private blockLoop() { @@ -44,12 +51,13 @@ export class RpcStateClass { private walletLockedState() { this._rpc.state.observe('encryptionstatus') - .subscribe(status => { - this._rpc.state - .set('locked', ['Locked', 'Unlocked, staking only'].includes(status)); - this._rpc.state - .set('ui:coldstaking:stake', ['Unencrypted', 'Unlocked', 'Unlocked, staking only'].includes(status)); - }); + .takeWhile(() => !this.destroyed) + .subscribe(status => { + this._rpc.state + .set('locked', ['Locked', 'Unlocked, staking only'].includes(status)); + this._rpc.state + .set('ui:coldstaking:stake', ['Unencrypted', 'Unlocked', 'Unlocked, staking only'].includes(status)); + }); } /* @@ -58,34 +66,38 @@ export class RpcStateClass { * update the coldstaking state. */ private coldStakeHook() { - this._rpc.state.observe('locked').subscribe(locked => { - // TODO: replace with getcoldstakinginfo.enabled - if (locked === false) { - // only available if unlocked - this._rpc.call('walletsettings', ['changeaddress']) - .subscribe( - // set state for coldstaking - response => this._rpc.state.set('ui:coldstaking', - response.changeaddress === 'default' - ? undefined - : !!response.changeaddress.coldstakingaddress - ), - error => this.log.er('walletsettings changeaddress', error) - ); - } - }); + this._rpc.state.observe('locked') + .takeWhile(() => !this.destroyed) + .subscribe(locked => { + // TODO: replace with getcoldstakinginfo.enabled + if (locked === false) { + // only available if unlocked + this._rpc.call('walletsettings', ['changeaddress']) + .subscribe( + // set state for coldstaking + response => this._rpc.state.set('ui:coldstaking', + response.changeaddress === 'default' + ? undefined + : !!response.changeaddress.coldstakingaddress + ), + error => this.log.er('walletsettings changeaddress', error) + ); + } + }); } private initWalletState() { - this._rpc.state.observe('encryptionstatus').take(1).subscribe(status => { - this._rpc.call('getwalletinfo').subscribe(response => { - // check if account is active - if (!!response.hdmasterkeyid) { - this._rpc.state.set('ui:walletInitialized', true); - } else { - this._rpc.state.set('ui:walletInitialized', false); - } - }, error => this.log.er('RPC Call returned an error', error)); - }); + this._rpc.state.observe('encryptionstatus') + .takeWhile(() => !this.destroyed) + .subscribe(status => { + this._rpc.call('getwalletinfo').subscribe(response => { + // check if account is active + if (!!response.hdmasterkeyid) { + this._rpc.state.set('ui:walletInitialized', true); + } else { + this._rpc.state.set('ui:walletInitialized', false); + } + }, error => this.log.er('RPC Call returned an error', error)); + }); } } diff --git a/src/app/core/rpc/rpc.service.ts b/src/app/core/rpc/rpc.service.ts index d40ae97b76..bb44c9018e 100644 --- a/src/app/core/rpc/rpc.service.ts +++ b/src/app/core/rpc/rpc.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, OnDestroy } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Log } from 'ng2-logger'; import { Subject } from 'rxjs/Subject'; @@ -26,7 +26,7 @@ declare global { */ @Injectable() -export class RpcService { +export class RpcService implements OnDestroy { /** * IP/URL for daemon (default = localhost) */ @@ -53,6 +53,7 @@ export class RpcService { public errorsStateCall: Subject = new Subject(); private _rpcState: RpcStateClass; + private destroyed: boolean = false; constructor( private _http: HttpClient, @@ -64,6 +65,10 @@ export class RpcService { this.toggleState(true); } + ngOnDestroy() { + this.destroyed = true; + } + /** * The call method will perform a single call to the particld daemon and perform a callback to * the instance through the function as defined in the params. @@ -170,8 +175,12 @@ export class RpcService { // initiate loop _call(); } else { - this.state.observe('blocks') .subscribe(success => this.stateCall(method, true)); - this.state.observe('txcount').subscribe(success => this.stateCall(method, true)); + this.state.observe('blocks') + .takeWhile(() => !this.destroyed) + .subscribe(success => this.stateCall(method, true)); + this.state.observe('txcount') + .takeWhile(() => !this.destroyed) + .subscribe(success => this.stateCall(method, true)); } } diff --git a/src/app/modals/createwallet/createwallet.component.ts b/src/app/modals/createwallet/createwallet.component.ts index 3e56e9acd1..558965bfa8 100644 --- a/src/app/modals/createwallet/createwallet.component.ts +++ b/src/app/modals/createwallet/createwallet.component.ts @@ -1,4 +1,7 @@ -import { Component, Inject, forwardRef, ViewChild, ElementRef, ComponentRef, HostListener } from '@angular/core'; +import { + Component, Inject, forwardRef, ViewChild, ElementRef, ComponentRef, HostListener, + OnDestroy +} from '@angular/core'; import { Log } from 'ng2-logger'; import { MatDialogRef } from '@angular/material'; @@ -20,7 +23,7 @@ import { SnackbarService } from '../../core/snackbar/snackbar.service'; styleUrls: ['./createwallet.component.scss'], animations: [slideDown()] }) -export class CreateWalletComponent { +export class CreateWalletComponent implements OnDestroy { log: any = Log.create('createwallet.component'); @@ -47,6 +50,7 @@ export class CreateWalletComponent { private validating: boolean = false; errorString: string = ''; + private destroyed: boolean = false; constructor ( @Inject(forwardRef(() => ModalsService)) @@ -59,6 +63,10 @@ export class CreateWalletComponent { this.reset(); } + ngOnDestroy() { + this.destroyed = true; + } + reset(): void { this._modalsService.enableClose = true; this.state.set('modal:fullWidth:enableClose', true); @@ -69,7 +77,8 @@ export class CreateWalletComponent { this.passwordVerify = ''; this.errorString = ''; this.step = 0; - this.state.observe('encryptionstatus').take(2) + this.state.observe('encryptionstatus') + .takeWhile(() => !this.destroyed) .subscribe(status => this.isCrypted = status !== 'Unencrypted'); } diff --git a/src/app/modals/modals.component.ts b/src/app/modals/modals.component.ts index 393ab052d4..21bcf5fd69 100644 --- a/src/app/modals/modals.component.ts +++ b/src/app/modals/modals.component.ts @@ -7,7 +7,7 @@ import { ElementRef, HostListener, ViewChild, - ViewContainerRef + ViewContainerRef, OnDestroy } from '@angular/core'; import { Log } from 'ng2-logger' @@ -33,7 +33,7 @@ import { StateService } from '../core/core.module'; EncryptwalletComponent ] }) -export class ModalsComponent implements DoCheck, OnInit { +export class ModalsComponent implements DoCheck, OnInit, OnDestroy { @ViewChild('modalContainer', { read: ViewContainerRef }) private modalContainer: ViewContainerRef; @@ -44,6 +44,7 @@ export class ModalsComponent implements DoCheck, OnInit { enableClose: boolean; loadSpinner: boolean; private log: any = Log.create('modals.component'); + private destroyed: boolean = false; constructor( private _element: ElementRef, @@ -54,9 +55,11 @@ export class ModalsComponent implements DoCheck, OnInit { ngOnInit(): void { this.state.observe('modal:fullWidth:enableClose') + .takeWhile(() => !this.destroyed) .subscribe(status => this.enableClose = status); this.state.observe('ui:spinner') + .takeWhile(() => !this.destroyed) .subscribe(status => this.loadSpinner = status); } @@ -70,6 +73,10 @@ export class ModalsComponent implements DoCheck, OnInit { } } + ngOnDestroy() { + this.destroyed = true; + } + // open modal open(message: any, data?: any): void { this.log.d(`open modal ${message.name}` + (data ? ` with data ${data}` : '')); diff --git a/src/app/modals/modals.service.ts b/src/app/modals/modals.service.ts index c6d7376ad5..39dfa2197a 100644 --- a/src/app/modals/modals.service.ts +++ b/src/app/modals/modals.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, OnDestroy } from '@angular/core'; import { Subject } from 'rxjs/Subject'; import { Log } from 'ng2-logger'; @@ -17,7 +17,7 @@ import { MatDialog } from '@angular/material'; import { ModalsComponent } from './modals.component'; @Injectable() -export class ModalsService { +export class ModalsService implements OnDestroy { public modal: any = null; private message: Subject = new Subject(); @@ -30,6 +30,7 @@ export class ModalsService { /* True if user already has a wallet (imported seed or created wallet) */ public initializedWallet: boolean = false; private data: string; + private destroyed: boolean = false; private log: any = Log.create('modals.service'); @@ -68,6 +69,10 @@ export class ModalsService { }); } + ngOnDestroy() { + this.destroyed = true; + } + /** * Open a modal * @param {string} modal The name of the modal to open @@ -150,6 +155,7 @@ export class ModalsService { */ openInitialCreateWallet(): void { this._rpc.state.observe('ui:walletInitialized') + .takeWhile(() => !this.destroyed) .subscribe( state => { this.initializedWallet = state; diff --git a/src/app/modals/shared/password/password.component.ts b/src/app/modals/shared/password/password.component.ts index b38f8aadf0..eac74a8142 100644 --- a/src/app/modals/shared/password/password.component.ts +++ b/src/app/modals/shared/password/password.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, HostListener, Input, Output } from '@angular/core'; +import { Component, EventEmitter, HostListener, Input, OnDestroy, Output } from '@angular/core'; import { Log } from 'ng2-logger'; import { IPassword } from './password.interface'; @@ -6,17 +6,17 @@ import { IPassword } from './password.interface'; import { RpcService } from '../../../core/core.module'; import { SnackbarService } from '../../../core/snackbar/snackbar.service'; - @Component({ selector: 'app-password', templateUrl: './password.component.html', styleUrls: ['./password.component.scss'] }) -export class PasswordComponent { +export class PasswordComponent implements OnDestroy { // UI State password: string; + private destroyed: boolean = false; @Input() showPass: boolean = false; @Input() label: string = 'Your Wallet password'; @@ -49,6 +49,10 @@ export class PasswordComponent { private flashNotification: SnackbarService) { } + ngOnDestroy() { + this.destroyed = true; + } + /** Get the input type - password or text */ getInputType(): string { return (this.showPass ? 'text' : 'password'); diff --git a/src/app/modals/syncing/syncing.component.ts b/src/app/modals/syncing/syncing.component.ts index 7a31a5782e..a69175fa6c 100644 --- a/src/app/modals/syncing/syncing.component.ts +++ b/src/app/modals/syncing/syncing.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy } from '@angular/core'; import { StateService, BlockStatusService } from '../../core/core.module'; @@ -9,7 +9,7 @@ import { Log } from 'ng2-logger'; templateUrl: './syncing.component.html', styleUrls: ['./syncing.component.scss'] }) -export class SyncingComponent { +export class SyncingComponent implements OnDestroy { log: any = Log.create('syncing.component'); @@ -20,12 +20,14 @@ export class SyncingComponent { manuallyOpened: boolean; syncPercentage: number; nPeers: number; + private destroyed: boolean = false; constructor( private _blockStatusService: BlockStatusService, private _state: StateService ) { _state.observe('connections') + .takeWhile(() => !this.destroyed) .subscribe(connections => this.nPeers = connections); this._blockStatusService.statusUpdates.asObservable().subscribe(status => { @@ -52,4 +54,8 @@ export class SyncingComponent { } }); } + + ngOnDestroy() { + this.destroyed = true; + } } diff --git a/src/app/wallet/wallet/shared/transaction.service.ts b/src/app/wallet/wallet/shared/transaction.service.ts index 69a8286e24..cfb79f93ba 100644 --- a/src/app/wallet/wallet/shared/transaction.service.ts +++ b/src/app/wallet/wallet/shared/transaction.service.ts @@ -56,27 +56,29 @@ export class TransactionService implements OnDestroy { this.txCount = txcount; } if (txcount > this.txCount) { - this.txCount = txcount; - this.newTransaction(); - } else { - this.loading = true; - this.log.d(`observing txcount, txs array: ${this.txs.length}`); - this.rpc_update(); - } + this.txCount = txcount; + this.newTransaction(); + } else { + this.loading = true; + this.log.d(`observing txcount, txs array: ${this.txs.length}`); + this.rpc_update(); + } // this.txCount = txcount; }); // It doesn't get called sometimes ? // this.rpc.state.observe('blocks').throttle(val => Observable.interval(30000/*ms*/)).subscribe(block => { - this.rpc.state.observe('blocks').takeWhile(() => !this.destroyed).subscribe(block => { - if (this.block === undefined) { - this.block = block; - } - if (block > this.block) { - this.checkBlock = true; - this.rpc_update() - } - }); + this.rpc.state.observe('blocks') + .takeWhile(() => !this.destroyed) + .subscribe(block => { + if (this.block === undefined) { + this.block = block; + } + if (block > this.block) { + this.checkBlock = true; + this.rpc_update() + } + }); /* check if testnet -> block explorer url */ this.rpc.state.observe('chain').take(1)