Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 1x | import { ApplicationRef, DOCUMENT, inject, Injectable, NgZone } from '@angular/core'; import { MatSnackBar, MatSnackBarConfig, MatSnackBarRef, TextOnlySnackBar } from '@angular/material/snack-bar'; import { SwUpdate } from '@angular/service-worker'; import { catchError, combineLatest, concat, defer, filter, first, from, interval, map, of, skip, switchMap, tap } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class AppServiceWorkerService { private readonly appRef = inject(ApplicationRef); private readonly service = inject(SwUpdate); private readonly snackBar = inject(MatSnackBar); private readonly zone = inject(NgZone); private readonly document = inject(DOCUMENT); /** * Application state stream. */ private readonly appStable$ = this.appRef.isStable.pipe(first(isStable => isStable)); /** * Service worker update interval. */ private get updateInterval() { const hours = 6; const minutes = 60; const seconds = 60; const miliseconds = 1000; return hours * minutes * seconds * miliseconds; } /** * Service worker update interval stream. */ private readonly updateInterval$ = interval(this.updateInterval); /** * Application version update stream. * Streams version updates and displays messages in the UI. */ private readonly versionUpdate$ = this.service.versionUpdates.pipe( map(event => { switch (event.type) { case 'VERSION_DETECTED': this.displaySnackBar(`Downloading new app version: ${event.version.hash}`); return null; case 'VERSION_INSTALLATION_FAILED': this.displaySnackBar(`Failed to install app version '${event.version.hash}': ${event.error}`); return null; case 'VERSION_READY': this.displaySnackBar(`New app version ready for use: ${event.latestVersion.hash}`); return event; default: return null; } }), ); /** * Application update check stream. * Checks for available update and prompts the user to update the application. */ private readonly checkForUpdates$ = concat(this.appStable$, this.updateInterval$).pipe( switchMap(() => from(defer(() => this.service.checkForUpdate()))), catchError((error: Error) => { this.displaySnackBar(`Failed to check for updates. ${error.message}`, void 0); return of(false); }), filter(update => update), skip(1), tap(() => { this.displaySnackBar( 'There is an application update available. It is recommended to update to avoid unexpected behavior.', 'Update', { duration: Number(Infinity) }, true, ); }), ); /** * Should be used in the application root component. */ public readonly subscribeToUpdates$ = combineLatest([this.versionUpdate$, this.checkForUpdates$]); /** * Displays a snackbar. * @param message message to display * @param action snackbar action * @param config snackbar config * @param refreshPage whether to refresh the page on snackbar action */ private displaySnackBar(message: string, action?: string, config?: MatSnackBarConfig, refreshPage?: boolean): void { this.zone.run(() => { const bar = this.snackBar.open(message, action, config); if (refreshPage === true) { this.refreshPage(bar); } }); } /** * Refreshes the page on snackbar action. * @param ref snackbar reference */ private refreshPage(ref: MatSnackBarRef<TextOnlySnackBar>): void { void ref .onAction() .pipe( tap(() => { this.document.location.reload(); }), ) .subscribe(); } } |