Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(aio): add service worker guide #20736

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 37 additions & 0 deletions aio/content/examples/service-worker-getstart/e2e/app.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { AppPage } from './app.po';
import { browser, element, by } from 'protractor';

describe('sw-example App', () => {
let page: AppPage;
let logo = element(by.css('img'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is only used once, why not move it inside the respective test?


beforeEach(() => {
page = new AppPage();
});

it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to Service Workers!');
});

it('should display the Angular logo', () => {
page.navigateTo();
expect(logo.isPresent()).toBe(true);
});

it('should show a header for the list of links', function () {
const listHeader = element(by.css('app-root > h2'));
expect(listHeader.getText()).toEqual('Here are some links to help you start:');
});

it('should show a list of links', function () {
element.all(by.css('ul > li > h2 > a')).then(function(items) {
expect(items.length).toBe(4);
expect(items[0].getText()).toBe('Angular Service Worker Intro');
expect(items[1].getText()).toBe('Tour of Heroes');
expect(items[2].getText()).toBe('CLI Documentation');
expect(items[3].getText()).toBe('Angular blog');
});
});

});
Copy link
Member

@gkalpak gkalpak Dec 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should somehow include the services into the tests. Right now, the only ServiceWorker related thing that is tested is ServiceWorkerModule.register(...). We should include the services into the app (i.e. inject them somewhere) and ensure that they at least do not break.

Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
Welcome to {{title}}!
</h1>
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
<h2><a target="_blank" rel="noopener" href="https://angular.io/service-worker-intro">Angular Service Worker Intro</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
</li>
</ul>

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be Service Workers? Are these tests run at all (I don't think so). Do we need them (given that they are pretty basic and the assertions could/should be covered in the e2e tests)?

}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is not needed, let's remove it.

})
export class AppComponent {
title = 'Service Workers';
}
31 changes: 31 additions & 0 deletions aio/content/examples/service-worker-getstart/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

// #docregion sw-import
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
// #enddocregion sw-import

import { CheckForUpdateService } from './check-for-update.service';
import { LogUpdateService } from './log-update.service';
import { PromptUpdateService } from './prompt-update.service';

// #docregion sw-module
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
ServiceWorkerModule.register('/ngsw-worker.js', {enabled: environment.production})
],
providers: [
CheckForUpdateService,
LogUpdateService,
PromptUpdateService,
],
bootstrap: [AppComponent]
})
export class AppModule { }
// #enddocregion sw-module
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to replace the above 2 lines with: import { interval } from 'rxjs/observable/interval';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The app fails to compile when we remove the import Observable line. So keeping it as is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It fails to compile, because you still mention Observable.interval below. It should be changed to just interval (in addition to replacing the above two lines per my previous comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok


function promptUser(event): boolean {
return true;
}

// #docregion sw-check-update
@Injectable()
export class CheckForUpdateService {

constructor(updates: SwUpdate) {
Observable.interval(6 * 60 * 60).subscribe(() => updates.checkForUpdate());
}
}
// #enddocregion sw-check-update
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';

// #docregion sw-update
@Injectable()
export class LogUpdateService {

constructor(updates: SwUpdate) {
updates.available.subscribe(event => {
console.log('current version is', event.current);
console.log('available version is', event.available);
});
updates.activated.subscribe(event => {
console.log('old version was', event.previous);
console.log('new version is', event.current);
});
}
}
// #enddocregion sw-update
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';

function promptUser(event): boolean {
return true;
}

// #docregion sw-activate
@Injectable()
export class PromptUpdateService {

constructor(updates: SwUpdate) {
updates.available.subscribe(event => {
if (promptUser(event)) {
updates.activateUpdate().then(() => document.location.reload());
}
});
}
}
// #enddocregion sw-activate
14 changes: 14 additions & 0 deletions aio/content/examples/service-worker-getstart/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SwExample</title>
<base href="/">

<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary?

</head>
<body>
<app-root></app-root>
</body>
</html>
12 changes: 12 additions & 0 deletions aio/content/examples/service-worker-getstart/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
28 changes: 28 additions & 0 deletions aio/content/examples/service-worker-getstart/src/ngsw-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

{
"index": "/index.html",
"assetGroups": [{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does index.html have to be in here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes - Checked with Alex

],
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**"
]
}
}]
}
41 changes: 41 additions & 0 deletions aio/content/guide/service-worker-comm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Communicating with service workers

Importing `ServiceWorkerModule` into your `AppModule` doesn't just register the service worker, it also provides a few services you can use to interact with the service worker and control the caching of your app.

## `SwUpdate` service

The `SwUpdate` service gives you access to events that indicate when the service worker has discovered an available update for your app or when it has activated such an update&mdash;meaning it is now serving content from that update to your app.

The `SwUpdate` service supports four separate operations:
* Getting notified of *available* updates. These are new versions of the app to be loaded if the page is refreshed.
* Getting notified of update *activation*. This is when the service worker starts serving a new version of the app immediately.
* Asking the service worker to check the server for new updates.
* Asking the service worker to activate the latest version of the app for the current tab.

### Available and activated updates

The two update events, `available` and `activated`, are `Observable` properties of `SwUpdate`:

<code-example path="service-worker-getstart/src/app/log-update.service.ts" linenums="false" title="log-update.service.ts" region="sw-update"> </code-example>


You can use these events to notify the user of a pending update or to refresh their pages when the code they are running is out of date.

### Checking for updates

It's possible to ask the service worker to check if any updates have been deployed to the server. You might choose to do this if you have a site that changes frequently or want updates to happen on a schedule.

Do this with the `checkForUpdate()` method:

<code-example path="service-worker-getstart/src/app/check-for-update.service.ts" linenums="false" title="check-for-update.service.ts" region="sw-check-update"> </code-example>


This method returns a `Promise` which indicates that the update check has completed successfully, though it does not indicate whether an update was discovered as a result of the check. Even if one is found, the service worker must still successfully download the changed files, which can fail. If successful, the `available` event will indicate availability of a new version of the app.

### Forcing update activation

If the current tab needs to be updated to the latest app version immediately, it can ask to do so with the `activateUpdate()` method:

<code-example path="service-worker-getstart/src/app/prompt-update.service.ts" linenums="false" title="prompt-update.service.ts" region="sw-activate"> </code-example>

Doing this could break lazy-loading into currently running apps, especially if the lazy-loaded chunks use filenames with hashes, which change every version.