From 58d4ece28d3f2914f41b9224bcd75e99cf9210d0 Mon Sep 17 00:00:00 2001 From: George Date: Fri, 9 Jun 2023 17:10:15 +0300 Subject: [PATCH] RED-6802, fixed the reconnect notification changing rapidly. --- .../connection-status.component.ts | 5 +- src/lib/error/error.service.ts | 82 +++++++++++++------ 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/src/lib/error/connection-status/connection-status.component.ts b/src/lib/error/connection-status/connection-status.component.ts index a70a323..04f3e36 100644 --- a/src/lib/error/connection-status/connection-status.component.ts +++ b/src/lib/error/connection-status/connection-status.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { connectionStatusTranslations } from '../../translations'; import { animate, state, style, transition, trigger } from '@angular/animations'; import { ErrorService } from '../error.service'; @@ -19,6 +19,5 @@ import { ErrorService } from '../error.service'; }) export class ConnectionStatusComponent { connectionStatusTranslations = connectionStatusTranslations; - - constructor(readonly errorService: ErrorService) {} + protected readonly errorService = inject(ErrorService); } diff --git a/src/lib/error/error.service.ts b/src/lib/error/error.service.ts index cff1a34..f19a90c 100644 --- a/src/lib/error/error.service.ts +++ b/src/lib/error/error.service.ts @@ -1,10 +1,11 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { fromEvent, merge, Observable, Subject } from 'rxjs'; import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { LoadingService } from '../loading'; -import { delay, filter, map } from 'rxjs/operators'; +import { filter, map, tap } from 'rxjs/operators'; import { NavigationStart, Router } from '@angular/router'; import { shareLast } from '../utils'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; export class CustomError { readonly label: string; @@ -39,41 +40,72 @@ export class ErrorService { readonly offline$: Observable; readonly online$: Observable; readonly connectionStatus$: Observable; - private readonly _error$ = new Subject(); - readonly error$ = this._error$.pipe(filter(error => !error || !isOffline(error))); - private readonly _online$ = new Subject(); + readonly #error$ = new Subject(); + readonly error$ = this.#error$.pipe(filter(error => !error || !isOffline(error))); + readonly #online$ = new Subject(); + readonly #loadingService = inject(LoadingService); + readonly #router = inject(Router); + readonly #clearNotification$ = new Subject(); + // eslint-disable-next-line no-undef + #timeout: NodeJS.Timeout | undefined; - constructor(private readonly _loadingService: LoadingService, private readonly _router: Router) { - _router.events.pipe(filter(event => event instanceof NavigationStart)).subscribe(() => { - this.clear(); - }); - this.offline$ = this._offline(); - this.online$ = this._online(); - const removeIndicator$ = this.online$.pipe( - delay(3000), - map(() => undefined), + constructor() { + this.#router.events + .pipe( + filter(event => event instanceof NavigationStart), + tap(() => this.clear()), + takeUntilDestroyed(), + ) + // eslint-disable-next-line rxjs/no-ignored-subscription + .subscribe(); + + this.offline$ = this.#offline(); + this.online$ = this.#online(); + + this.connectionStatus$ = merge(this.online$, this.offline$, this.#clearNotification$.asObservable()).pipe( + map(event => event?.type), + filter(type => { + if (type === 'online') { + this.#setOnlineTimeout(); + return false; + } + this.#clearOnlineTimeout(); + return true; + }), + // used display-online when I manually generate a new event to bypass the above filter + map(type => (type === 'display-online' ? 'online' : type)), ); - - this.connectionStatus$ = merge(this.online$, this.offline$, removeIndicator$).pipe(map(event => event?.type)); } set(error: ErrorType): void { - this._loadingService.stop(); - this._error$.next(error); + this.#loadingService.stop(); + this.#error$.next(error); } setOnline(): void { - this._online$.next(true); + this.#online$.next(true); } clear(): void { - this._error$.next(undefined); + this.#error$.next(undefined); } - private _offline() { + #setOnlineTimeout() { + this.#timeout ??= setTimeout(() => { + this.#online$.next(new Event('display-online')); + setTimeout(() => this.#clearNotification$.next(undefined), 3000); + }, 3000); + } + + #clearOnlineTimeout() { + clearTimeout(this.#timeout); + this.#timeout = undefined; + } + + #offline() { return merge( fromEvent(window, 'offline'), - this._error$.pipe( + this.#error$.pipe( filter(isOffline), map(() => new Event('offline')), shareLast(), @@ -81,7 +113,9 @@ export class ErrorService { ); } - private _online() { - return merge(fromEvent(window, 'online'), this._online$.pipe(map(() => new Event('online')))).pipe(shareLast()); + #online() { + return merge(fromEvent(window, 'online'), this.#online$.pipe(map(v => (v instanceof Event ? v : new Event('online'))))).pipe( + shareLast(), + ); } }