Skip to content

Commit 5e7fc25

Browse files
jsaguetthePunderWoman
authored andcommitted
feat(service-worker): add function to provide service worker (#48247)
add function `provideServiceWorker` to register a service worker in standalone applications without using `ServiceWorkerModule.register()` PR Close #48247
1 parent 86fc4d3 commit 5e7fc25

File tree

7 files changed

+566
-495
lines changed

7 files changed

+566
-495
lines changed

goldens/public-api/service-worker/index.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
```ts
66

7+
import { EnvironmentProviders } from '@angular/core';
78
import * as i0 from '@angular/core';
89
import { ModuleWithProviders } from '@angular/core';
910
import { Observable } from 'rxjs';
@@ -19,9 +20,12 @@ export interface NoNewVersionDetectedEvent {
1920
};
2021
}
2122

23+
// @public
24+
export function provideServiceWorker(script: string, options?: SwRegistrationOptions): EnvironmentProviders;
25+
2226
// @public (undocumented)
2327
export class ServiceWorkerModule {
24-
static register(script: string, opts?: SwRegistrationOptions): ModuleWithProviders<ServiceWorkerModule>;
28+
static register(script: string, options?: SwRegistrationOptions): ModuleWithProviders<ServiceWorkerModule>;
2529
// (undocumented)
2630
static ɵfac: i0.ɵɵFactoryDeclaration<ServiceWorkerModule, never>;
2731
// (undocumented)

packages/service-worker/src/index.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,8 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
/**
10-
* @license
11-
* Copyright Google LLC All Rights Reserved.
12-
*
13-
* Use of this source code is governed by an MIT-style license that can be
14-
* found in the LICENSE file at https://angular.io/license
15-
*/
16-
179
export {NoNewVersionDetectedEvent, UnrecoverableStateEvent, UpdateActivatedEvent, UpdateAvailableEvent, VersionDetectedEvent, VersionEvent, VersionInstallationFailedEvent, VersionReadyEvent} from './low_level';
18-
export {ServiceWorkerModule, SwRegistrationOptions} from './module';
10+
export {ServiceWorkerModule} from './module';
11+
export {provideServiceWorker, SwRegistrationOptions} from './provider';
1912
export {SwPush} from './push';
2013
export {SwUpdate} from './update';

packages/service-worker/src/module.ts

Lines changed: 5 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -6,182 +6,28 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {isPlatformBrowser} from '@angular/common';
10-
import {APP_INITIALIZER, ApplicationRef, InjectionToken, Injector, ModuleWithProviders, NgModule, NgZone, PLATFORM_ID} from '@angular/core';
11-
import {merge, Observable, of} from 'rxjs';
12-
import {delay, filter, take} from 'rxjs/operators';
9+
import {ModuleWithProviders, NgModule} from '@angular/core';
1310

14-
import {NgswCommChannel} from './low_level';
11+
import {provideServiceWorker, SwRegistrationOptions} from './provider';
1512
import {SwPush} from './push';
1613
import {SwUpdate} from './update';
1714

18-
/**
19-
* Token that can be used to provide options for `ServiceWorkerModule` outside of
20-
* `ServiceWorkerModule.register()`.
21-
*
22-
* You can use this token to define a provider that generates the registration options at runtime,
23-
* for example via a function call:
24-
*
25-
* {@example service-worker/registration-options/module.ts region="registration-options"
26-
* header="app.module.ts"}
27-
*
28-
* @publicApi
29-
*/
30-
export abstract class SwRegistrationOptions {
31-
/**
32-
* Whether the ServiceWorker will be registered and the related services (such as `SwPush` and
33-
* `SwUpdate`) will attempt to communicate and interact with it.
34-
*
35-
* Default: true
36-
*/
37-
enabled?: boolean;
38-
39-
/**
40-
* A URL that defines the ServiceWorker's registration scope; that is, what range of URLs it can
41-
* control. It will be used when calling
42-
* [ServiceWorkerContainer#register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register).
43-
*/
44-
scope?: string;
45-
46-
/**
47-
* Defines the ServiceWorker registration strategy, which determines when it will be registered
48-
* with the browser.
49-
*
50-
* The default behavior of registering once the application stabilizes (i.e. as soon as there are
51-
* no pending micro- and macro-tasks) is designed to register the ServiceWorker as soon as
52-
* possible but without affecting the application's first time load.
53-
*
54-
* Still, there might be cases where you want more control over when the ServiceWorker is
55-
* registered (for example, there might be a long-running timeout or polling interval, preventing
56-
* the app from stabilizing). The available option are:
57-
*
58-
* - `registerWhenStable:<timeout>`: Register as soon as the application stabilizes (no pending
59-
* micro-/macro-tasks) but no later than `<timeout>` milliseconds. If the app hasn't
60-
* stabilized after `<timeout>` milliseconds (for example, due to a recurrent asynchronous
61-
* task), the ServiceWorker will be registered anyway.
62-
* If `<timeout>` is omitted, the ServiceWorker will only be registered once the app
63-
* stabilizes.
64-
* - `registerImmediately`: Register immediately.
65-
* - `registerWithDelay:<timeout>`: Register with a delay of `<timeout>` milliseconds. For
66-
* example, use `registerWithDelay:5000` to register the ServiceWorker after 5 seconds. If
67-
* `<timeout>` is omitted, is defaults to `0`, which will register the ServiceWorker as soon
68-
* as possible but still asynchronously, once all pending micro-tasks are completed.
69-
* - An [Observable](guide/observables) factory function: A function that returns an `Observable`.
70-
* The function will be used at runtime to obtain and subscribe to the `Observable` and the
71-
* ServiceWorker will be registered as soon as the first value is emitted.
72-
*
73-
* Default: 'registerWhenStable:30000'
74-
*/
75-
registrationStrategy?: string|(() => Observable<unknown>);
76-
}
77-
78-
export const SCRIPT = new InjectionToken<string>('NGSW_REGISTER_SCRIPT');
79-
80-
export function ngswAppInitializer(
81-
injector: Injector, script: string, options: SwRegistrationOptions,
82-
platformId: string): Function {
83-
return () => {
84-
if (!(isPlatformBrowser(platformId) && ('serviceWorker' in navigator) &&
85-
options.enabled !== false)) {
86-
return;
87-
}
88-
89-
// Wait for service worker controller changes, and fire an INITIALIZE action when a new SW
90-
// becomes active. This allows the SW to initialize itself even if there is no application
91-
// traffic.
92-
navigator.serviceWorker.addEventListener('controllerchange', () => {
93-
if (navigator.serviceWorker.controller !== null) {
94-
navigator.serviceWorker.controller.postMessage({action: 'INITIALIZE'});
95-
}
96-
});
97-
98-
let readyToRegister$: Observable<unknown>;
99-
100-
if (typeof options.registrationStrategy === 'function') {
101-
readyToRegister$ = options.registrationStrategy();
102-
} else {
103-
const [strategy, ...args] =
104-
(options.registrationStrategy || 'registerWhenStable:30000').split(':');
105-
106-
switch (strategy) {
107-
case 'registerImmediately':
108-
readyToRegister$ = of(null);
109-
break;
110-
case 'registerWithDelay':
111-
readyToRegister$ = delayWithTimeout(+args[0] || 0);
112-
break;
113-
case 'registerWhenStable':
114-
readyToRegister$ = !args[0] ? whenStable(injector) :
115-
merge(whenStable(injector), delayWithTimeout(+args[0]));
116-
break;
117-
default:
118-
// Unknown strategy.
119-
throw new Error(
120-
`Unknown ServiceWorker registration strategy: ${options.registrationStrategy}`);
121-
}
122-
}
123-
124-
// Don't return anything to avoid blocking the application until the SW is registered.
125-
// Also, run outside the Angular zone to avoid preventing the app from stabilizing (especially
126-
// given that some registration strategies wait for the app to stabilize).
127-
// Catch and log the error if SW registration fails to avoid uncaught rejection warning.
128-
const ngZone = injector.get(NgZone);
129-
ngZone.runOutsideAngular(
130-
() => readyToRegister$.pipe(take(1)).subscribe(
131-
() =>
132-
navigator.serviceWorker.register(script, {scope: options.scope})
133-
.catch(err => console.error('Service worker registration failed with:', err))));
134-
};
135-
}
136-
137-
function delayWithTimeout(timeout: number): Observable<unknown> {
138-
return of(null).pipe(delay(timeout));
139-
}
140-
141-
function whenStable(injector: Injector): Observable<unknown> {
142-
const appRef = injector.get(ApplicationRef);
143-
return appRef.isStable.pipe(filter(stable => stable));
144-
}
145-
146-
export function ngswCommChannelFactory(
147-
opts: SwRegistrationOptions, platformId: string): NgswCommChannel {
148-
return new NgswCommChannel(
149-
isPlatformBrowser(platformId) && opts.enabled !== false ? navigator.serviceWorker :
150-
undefined);
151-
}
152-
15315
/**
15416
* @publicApi
15517
*/
156-
@NgModule({
157-
providers: [SwPush, SwUpdate],
158-
})
18+
@NgModule({providers: [SwPush, SwUpdate]})
15919
export class ServiceWorkerModule {
16020
/**
16121
* Register the given Angular Service Worker script.
16222
*
16323
* If `enabled` is set to `false` in the given options, the module will behave as if service
16424
* workers are not supported by the browser, and the service worker will not be registered.
16525
*/
166-
static register(script: string, opts: SwRegistrationOptions = {}):
26+
static register(script: string, options: SwRegistrationOptions = {}):
16727
ModuleWithProviders<ServiceWorkerModule> {
16828
return {
16929
ngModule: ServiceWorkerModule,
170-
providers: [
171-
{provide: SCRIPT, useValue: script},
172-
{provide: SwRegistrationOptions, useValue: opts},
173-
{
174-
provide: NgswCommChannel,
175-
useFactory: ngswCommChannelFactory,
176-
deps: [SwRegistrationOptions, PLATFORM_ID]
177-
},
178-
{
179-
provide: APP_INITIALIZER,
180-
useFactory: ngswAppInitializer,
181-
deps: [Injector, SCRIPT, SwRegistrationOptions, PLATFORM_ID],
182-
multi: true,
183-
},
184-
],
30+
providers: [provideServiceWorker(script, options)],
18531
};
18632
}
18733
}

0 commit comments

Comments
 (0)