From 8f120aff33c3644b82f4d2e2787c4c7ef9faac51 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 7 Nov 2018 22:45:40 +0200 Subject: [PATCH] refactor(service-worker): DRY up SW registration logic (#21842) PR Close #21842 --- aio/content/guide/service-worker-config.md | 12 --- packages/service-worker/src/module.ts | 83 ++++++++++++------- .../service-worker/service-worker.d.ts | 2 +- 3 files changed, 53 insertions(+), 44 deletions(-) diff --git a/aio/content/guide/service-worker-config.md b/aio/content/guide/service-worker-config.md index 1cb77cfb56..c2273b28ca 100644 --- a/aio/content/guide/service-worker-config.md +++ b/aio/content/guide/service-worker-config.md @@ -218,15 +218,3 @@ If the field is omitted, it defaults to: '!/**/*__*/**', // Exclude URLs containing `__` in any other segment. ] ``` - -## `register options` - - - You can pass some options to the `register()` method. -- enabled: optional parameter, by default is true, if enabled is false, the module will behave like the browser not support service worker, and service worker will not be registered. -- scope: optional parameter, to specify the subset of your content that you want the service worker to control. -- registrationStrategy: optional parameter, specify a strategy that determines when to register the service worker, the available options are: - - registerWhenStable: this is the default behavior, the service worker will register when the application is stable (no microTasks or macroTasks remain). - - registerImmediately: register immediately without waiting the application to become stable. - - registerDelay:timeout : register after the timeout period, `timeout` is the number of milliseconds to delay registration. For example `registerDelay:5000` would register the service worker after 5 seconds. If the number of `timeout` is not given (`registerDelay`), by default, `timeout` will be `0`, but it is not equal to `registerImmediately`, it will still run a `setTimeout(register, 0)` to wait all `microTasks` to finish then perform registration of the service worker. - - A factory to get Observable : you can also specify a factory which returns an Observable, the service worker will be registered the first time that a value is emitted by the Observable. \ No newline at end of file diff --git a/packages/service-worker/src/module.ts b/packages/service-worker/src/module.ts index 5d07f9e88e..39e8f62e65 100644 --- a/packages/service-worker/src/module.ts +++ b/packages/service-worker/src/module.ts @@ -8,8 +8,8 @@ import {isPlatformBrowser} from '@angular/common'; import {APP_INITIALIZER, ApplicationRef, InjectionToken, Injector, ModuleWithProviders, NgModule, PLATFORM_ID} from '@angular/core'; -import {Observable} from 'rxjs'; -import {filter, take} from 'rxjs/operators'; +import {Observable, of } from 'rxjs'; +import {delay, filter, take} from 'rxjs/operators'; import {NgswCommChannel} from './low_level'; import {SwPush} from './push'; @@ -43,7 +43,32 @@ export abstract class SwRegistrationOptions { */ scope?: string; - registrationStrategy?: (() => Observable)|string; + /** + * Defines the ServiceWorker registration strategy, which determines when it will be registered + * with the browser. + * + * The default behavior of registering once the application stabilizes (i.e. as soon as there are + * no pending micro- and macro-tasks), is designed register the ServiceWorker as soon as possible + * but without affecting the application's first time load. + * + * Still, there might be cases where you want more control over when the ServiceWorker is + * registered (e.g. there might be a long-running timeout or polling interval, preventing the app + * to stabilize). The available option are: + * + * - `registerWhenStable`: Register as soon as the application stabilizes (no pending + * micro-/macro-tasks). + * - `registerImmediately`: Register immediately. + * - `registerWithDelay:`: Register with a delay of `` milliseconds. For + * example, use `registerWithDelay:5000` to register the ServiceWorker after 5 seconds. If + * `` is omitted, is defaults to `0`, which will register the ServiceWorker as soon + * as possible but still asynchronously, once all pending micro-tasks are completed. + * - An [Observable](guide/observables) factory function: A function that returns an `Observable`. + * The function will be used at runtime to obtain and subscribe to the `Observable` and the + * ServiceWorker will be registered as soon as the first value is emitted. + * + * Default: 'registerWhenStable' + */ + registrationStrategy?: string|(() => Observable); } export const SCRIPT = new InjectionToken('NGSW_REGISTER_SCRIPT'); @@ -52,13 +77,10 @@ export function ngswAppInitializer( injector: Injector, script: string, options: SwRegistrationOptions, platformId: string): Function { const initializer = () => { - const app = injector.get(ApplicationRef); if (!(isPlatformBrowser(platformId) && ('serviceWorker' in navigator) && options.enabled !== false)) { return; } - const whenStable = - app.isStable.pipe(filter((stable: boolean) => !!stable), take(1)).toPromise(); // Wait for service worker controller changes, and fire an INITIALIZE action when a new SW // becomes active. This allows the SW to initialize itself even if there is no application @@ -69,35 +91,34 @@ export function ngswAppInitializer( } }); - // Don't return the Promise, as that will block the application until the SW is registered, and - // cause a crash if the SW registration fails. + let readyToRegister$: Observable; + if (typeof options.registrationStrategy === 'function') { - const observable = options.registrationStrategy(); - const subscription = observable.subscribe(() => { - navigator.serviceWorker.register(script, {scope: options.scope}); - subscription.unsubscribe(); - }); + readyToRegister$ = options.registrationStrategy(); } else { - const registrationStrategy = typeof options.registrationStrategy === 'string' ? - options.registrationStrategy : - 'registerWhenStable'; - if (registrationStrategy === 'registerWhenStable') { - whenStable.then(() => navigator.serviceWorker.register(script, {scope: options.scope})); - } else if (registrationStrategy === 'registerImmediately') { - navigator.serviceWorker.register(script, {scope: options.scope}); - } else if (registrationStrategy.indexOf('registerDelay') !== -1) { - const split = registrationStrategy.split(':'); - const delayStr = split.length > 1 ? split[1] : undefined; - const delay = Number(delayStr); - setTimeout( - () => navigator.serviceWorker.register(script, {scope: options.scope}), - typeof delay === 'number' ? delay : 0); - } else { - // wrong strategy - throw new Error( - `Unknown service worker registration strategy: ${options.registrationStrategy}`); + const [strategy, ...args] = (options.registrationStrategy || 'registerWhenStable').split(':'); + switch (strategy) { + case 'registerImmediately': + readyToRegister$ = of (null); + break; + case 'registerWithDelay': + readyToRegister$ = of (null).pipe(delay(+args[0] || 0)); + break; + case 'registerWhenStable': + const appRef = injector.get(ApplicationRef); + readyToRegister$ = appRef.isStable.pipe(filter(stable => stable)); + break; + default: + // Unknown strategy. + throw new Error( + `Unknown ServiceWorker registration strategy: ${options.registrationStrategy}`); } } + + // Don't return anything to avoid blocking the application until the SW is registered or + // causing a crash if the SW registration fails. + readyToRegister$.pipe(take(1)).subscribe( + () => navigator.serviceWorker.register(script, {scope: options.scope})); }; return initializer; } diff --git a/tools/public_api_guard/service-worker/service-worker.d.ts b/tools/public_api_guard/service-worker/service-worker.d.ts index 1ecb0bf761..06a389a790 100644 --- a/tools/public_api_guard/service-worker/service-worker.d.ts +++ b/tools/public_api_guard/service-worker/service-worker.d.ts @@ -21,7 +21,7 @@ export declare class SwPush { export declare abstract class SwRegistrationOptions { enabled?: boolean; - registrationStrategy?: (() => Observable) | string; + registrationStrategy?: string | (() => Observable); scope?: string; }