diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 6b7ec7f51..e3f585a25 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -2,6 +2,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { ModuleWithProviders, NgModule } from '@angular/core'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ZONE_SERVICE } from './injection-tokens'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './components/app/app.component'; import { ElectrsApiService } from './services/electrs-api.service'; @@ -13,6 +14,7 @@ import { WebsocketService } from './services/websocket.service'; import { AudioService } from './services/audio.service'; import { SeoService } from './services/seo.service'; import { OpenGraphService } from './services/opengraph.service'; +import { ZoneService } from './services/zone-shim.service'; import { SharedModule } from './shared/shared.module'; import { StorageService } from './services/storage.service'; import { HttpCacheInterceptor } from './services/http-cache.interceptor'; @@ -42,7 +44,8 @@ const providers = [ CapAddressPipe, AppPreloadingStrategy, ServicesApiServices, - { provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true } + { provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true }, + { provide: ZONE_SERVICE, useClass: ZoneService }, ]; @NgModule({ diff --git a/frontend/src/app/app.server.module.ts b/frontend/src/app/app.server.module.ts index 9833fd7b9..10ef157f4 100644 --- a/frontend/src/app/app.server.module.ts +++ b/frontend/src/app/app.server.module.ts @@ -2,9 +2,13 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { ServerModule } from '@angular/platform-server'; +import { ZONE_SERVICE } from './injection-tokens'; import { AppModule } from './app.module'; import { AppComponent } from './components/app/app.component'; import { HttpCacheInterceptor } from './services/http-cache.interceptor'; +import { StateService } from './services/state.service'; +import { ZoneService } from './services/zone.service'; + @NgModule({ imports: [ @@ -12,8 +16,9 @@ import { HttpCacheInterceptor } from './services/http-cache.interceptor'; ServerModule, ], providers: [ - { provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true } + { provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true }, + { provide: ZONE_SERVICE, useClass: ZoneService }, ], bootstrap: [AppComponent], }) -export class AppServerModule {} +export class AppServerModule {} \ No newline at end of file diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 375742deb..82f26d3d7 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, AfterViewInit, OnDestroy, HostListener, ViewChild, ElementRef } from '@angular/core'; +import { Component, OnInit, AfterViewInit, OnDestroy, HostListener, ViewChild, ElementRef, Inject, ChangeDetectorRef } from '@angular/core'; import { ElectrsApiService } from '../../services/electrs-api.service'; import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { @@ -28,6 +28,7 @@ import { Price, PriceService } from '../../services/price.service'; import { isFeatureActive } from '../../bitcoin.utils'; import { ServicesApiServices } from '../../services/services-api.service'; import { EnterpriseService } from '../../services/enterprise.service'; +import { ZONE_SERVICE } from '../../injection-tokens'; interface Pool { id: number; @@ -101,7 +102,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { inputIndex: number; outputIndex: number; graphExpanded: boolean = false; - graphWidth: number = 1000; + graphWidth: number = 1068; graphHeight: number = 360; inOutLimit: number = 150; maxInOut: number = 0; @@ -141,6 +142,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { private priceService: PriceService, private storageService: StorageService, private enterpriseService: EnterpriseService, + private cd: ChangeDetectorRef, + @Inject(ZONE_SERVICE) private zoneService: any, ) {} ngOnInit() { @@ -356,7 +359,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { } }); - this.subscription = this.route.paramMap + this.subscription = this.zoneService.wrapObservable(this.route.paramMap .pipe( switchMap((params: ParamMap) => { const urlMatch = (params.get('id') || '').split(':'); @@ -430,7 +433,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { } return of(tx); }) - ) + )) .subscribe((tx: Transaction) => { if (!tx) { this.fetchCachedTx$.next(this.txId); @@ -503,6 +506,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { ).subscribe(); setTimeout(() => { this.applyFragment(); }, 0); + + this.cd.detectChanges(); }, (error) => { this.error = error; @@ -785,9 +790,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { @HostListener('window:resize', ['$event']) setGraphSize(): void { this.isMobile = window.innerWidth < 850; - if (this.graphContainer?.nativeElement) { + if (this.graphContainer?.nativeElement && this.stateService.isBrowser) { setTimeout(() => { - if (this.graphContainer?.nativeElement) { + if (this.graphContainer?.nativeElement?.clientWidth) { this.graphWidth = this.graphContainer.nativeElement.clientWidth; } else { setTimeout(() => { this.setGraphSize(); }, 1); diff --git a/frontend/src/app/injection-tokens.ts b/frontend/src/app/injection-tokens.ts new file mode 100644 index 000000000..6d923fd45 --- /dev/null +++ b/frontend/src/app/injection-tokens.ts @@ -0,0 +1,3 @@ +import { InjectionToken } from '@angular/core'; + +export const ZONE_SERVICE = new InjectionToken('ZONE_TASK'); \ No newline at end of file diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 54fcad7d4..25829ea3a 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -59,6 +59,7 @@ export class WebsocketService { const theInitData = this.transferState.get(initData, null); if (theInitData) { + this.stateService.isLoadingWebSocket$.next(false); this.handleResponse(theInitData.body); this.startSubscription(false, true); } else { diff --git a/frontend/src/app/services/zone-shim.service.ts b/frontend/src/app/services/zone-shim.service.ts new file mode 100644 index 000000000..003d39f71 --- /dev/null +++ b/frontend/src/app/services/zone-shim.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ZoneService { + + constructor() { } + + wrapObservable(obs: Observable): Observable { + return obs; + } +} diff --git a/frontend/src/app/services/zone.service.ts b/frontend/src/app/services/zone.service.ts new file mode 100644 index 000000000..210c5f8f0 --- /dev/null +++ b/frontend/src/app/services/zone.service.ts @@ -0,0 +1,60 @@ +import { ApplicationRef, Injectable, NgZone } from '@angular/core'; +import { Observable, Subscriber } from 'rxjs'; + +// global Zone object provided by zone.js +declare const Zone: any; + +@Injectable({ + providedIn: 'root' +}) +export class ZoneService { + + constructor( + private ngZone: NgZone, + private appRef: ApplicationRef, + ) { } + + wrapObservable(obs: Observable): Observable { + return new Observable((subscriber: Subscriber) => { + let task: any; + + this.ngZone.run(() => { + task = Zone.current.scheduleMacroTask('wrapObservable', () => {}, {}, () => {}, () => {}); + }); + + const subscription = obs.subscribe( + value => { + subscriber.next(value); + if (task) { + this.ngZone.run(() => { + this.appRef.tick(); + }); + task.invoke(); + } + }, + err => { + subscriber.error(err); + if (task) { + this.appRef.tick(); + task.invoke(); + } + }, + () => { + subscriber.complete(); + if (task) { + this.appRef.tick(); + task.invoke(); + } + } + ); + + return () => { + subscription.unsubscribe(); + if (task) { + this.appRef.tick(); + task.invoke(); + } + }; + }); + } +}