Service Workers In Angular (v12+)

Service Workers in Angular


What are Service Workers?

Service Workers are scripts that run in the background of a web page and act as a proxy between the web page and the network. They allow developers to create offline-first web applications, where the app can work even when there is no network connection. Service Workers can also cache the web page's assets, such as images, CSS, and JavaScript files, so that the web application can load faster.

With service workers, developers can deliver web applications that provide consistent performance and functionality, regardless of the user's network status. By intelligently caching essential assets, service workers enable the application to load quickly, even in challenging network conditions. This caching mechanism also reduces the reliance on continuous network requests, further enhancing the user experience by minimizing data consumption.

Using Service Workers in Angular

To use Service Workers in an Angular application, we need to use the Angular Service Worker package. This package provides us with a set of tools and APIs that simplify the process of creating and managing Service Workers in our application.

To add Service Workers to an Angular application, we need to run the following command:

ng add @angular/pwa

This command installs the required packages and adds the necessary configuration files to our project.

Once the Service Worker is installed, we can configure it to cache our application's assets. We can do this by modifying the ngsw-config.json file, which is located in the src folder of our project.

Using the service worker as an auto update mechanism

A really effective way to quickly use your new service worker to add some value to your application is to use it as a mechanism for checking and updating your application source files on a users browsers

Browsers typically cache static web files, and while many technologies provide means of checking for updates, their reliability can be questionable. Taking control of this process becomes invaluable, especially when working on an application or platform that undergoes frequent deployments or receives regular feature updates.

By using a service worker, you can ensure a more reliable and efficient update mechanism for your application's source files. This approach empowers you to deliver the latest features and improvements to your users with ease and without relying on unreliable caching or manual interventions.

Understanding how this process works from the service workers perspective

  1. Caching Static Assets: When users first load your Angular application, the service worker caches the static assets required to run the application, such as HTML, CSS, and JavaScript files. These assets are stored locally on the user's device, ensuring faster subsequent loads and offline access.
  2. Update Check: By default, the Angular service worker automatically checks for updates once every 24 hours. However, you can customize this behavior to check for updates more frequently if desired. When an update is available, the service worker detects the change.
  3. Precaching New Code: When the service worker detects an update, it downloads the new version of your Angular application in the background. This process is known as "precaching." The service worker caches the new code and assets, ensuring they are available for subsequent visits.
  4. Prompting Users for Update: Once the new code is precached, the service worker can prompt users to update their browser's version of the application. This can be done by adding a notification or alert to inform users that a new version is available and asking them to reload the page.

Practical Demonstration

We start by opening up a terminal in the root of our angular application and running the following command

ng add @angular/pwa@13

Running the command above on our angular application will execute a variety of updates and changes to our code but the most notable ones are the following:

Modification of app.module.ts:

In order to enable service worker capabilities, we make modifications to the app.module.ts file. This involves adding necessary code and configurations to integrate the service worker functionality into our Angular application.

Creation of ngsw-config.json:

A new JSON configuration file, ngsw-config.json, is created specifically for the service worker. This file contains default configurations and settings for the service worker, allowing us to customize its behavior and caching strategies.

Update of angular.json file:

We update the angular.json file to incorporate the necessary configurations for service worker support. This ensures that the Angular build process includes the required files and settings to enable service worker functionality in our application.

By running just that one command , we can immediately enable service worker capabilities in our application without the need for additional development. Leveraging the power of the Angular CLI simplifies the process, although utilizing and implementing update mechanisms may require additional code and configurations to be implemented.

Implementing the polling logic

To begin, we can create a service dedicated to handling update polling and initiating the process. This approach provides a clean and organized way to initialize polling. Alternatively, you can incorporate this logic directly into your base or bootstrap component.

Once we have created the service, we can proceed with writing the logic that enables us to initialize the poll and check for updates using our service worker.

Firstly, we need to import the necessary dependencies and inject them into our constructor.

import { ApplicationRef, Injectable } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';

// constructor dependency injection
constructor(appRef: ApplicationRef, updates: SwUpdate) 

This step ensures that the required functionality and resources are available within our service. By importing and injecting the dependencies, we establish the foundation for implementing the logic to initialize the poll and initiate update checks using the service worker.

Then we need to create some constants

The first constant will give us information regarding whether our application has stabilized

// Allow the app to stabilize first, before starting polling for updates with `interval()`.
    const appIsStable$ = appRef.isStable.pipe(
      first((isStable) => isStable === true)
    );

The second constant creates the interval that will be used to determine how frequently to poll , we’re setting it up to poll every 6 minutes

 const everySixMinutes$ = interval(60000).pipe(take(100));

and finally we round off the implementation by combining the two constants which allows us to start polling once the app is stable and finally start checking for updates

const everySixMinutesOnceAppIsStable$ = concat(
      appIsStable$,
      everySixMinutes$
    );
    everySixMinutesOnceAppIsStable$.subscribe(() =>
      this.checkForUpdate(updates)
    );
  }

  public checkForUpdate(updates: SwUpdate): void {
    console.log('Polling for updates...');
    updates.checkForUpdate();
  }

When we’re done with the implementation our service should look like this

import { ApplicationRef, Injectable } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
import { interval, concat } from 'rxjs';
import { first, take } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class UpdateSourceService {
  constructor(appRef: ApplicationRef, updates: SwUpdate) {
    // Allow the app to stabilize first, before starting polling for updates with `interval()`.
    const appIsStable$ = appRef.isStable.pipe(
      first((isStable) => isStable === true)
    );
    const everySixMinutes$ = interval(60000).pipe(take(100));
    const everySixMinutesOnceAppIsStable$ = concat(
      appIsStable$,
      everySixMinutes$
    );
    everySixMinutesOnceAppIsStable$.subscribe(() =>
      this.checkForUpdate(updates)
    );
  }

  public checkForUpdate(updates: SwUpdate): void {
    console.log('Polling for updates...');
    updates.checkForUpdate();
  }
}

It's important to highlight that the polling logic must reside within a component that is guaranteed to be rendered and remain rendered for the polling to function effectively. The ideal location for this logic is the base component or main app component, which serves as the entry point of your application and renders the initial view.

By placing the update polling logic in the base component, you ensure that it remains active throughout the lifespan of your application, enabling seamless updates.

We're nearing completion! Having exposed the polling and update checking logic, we can effortlessly inject this service into our base component, initiating the polling process.

To finalize the implementation, we need to incorporate the functionality to reload our web application whenever new updates are received.

Finalising the update check implementation

We’re going to need some dependencies here so lets import those and inject them

import { UpdateSourceService } from '../../services/update-source.service';
import { SwUpdate } from '@angular/service-worker';
constructor(
    private updateSourceService: UpdateSourceService,
    private swUpdate: SwUpdate
  )

Now we need to implement the logic for listening to received updates, which will be triggered by our polling service. Finally, we will conclude with a function that reloads our web page and installs the new updates.

constructor(
    private updateSourceService: UpdateSourceService,
    private swUpdate: SwUpdate
  )
{
    this.swUpdate.available.subscribe((event) => {
      this.notificationService.showDefaultMessage('New update available');
      console.log('New update available');
      console.log(event);
      this.updateToLatest();
    });
  }

  private updateToLatest(): void {
    console.log('Updating to latest version.');
    this.notificationService.showDefaultMessage('Updating to latest version.')
    this.swUpdate.activateUpdate().then(() => document.location.reload());
  }

And that concludes our implementation. Let's recap the steps:

  1. We developed a polling service that checks for updates every 6 minutes.
  2. Upon receiving an update, we trigger a custom function.
  3. The custom function reloads our web page and installs the updated code.

Conclusion

In conclusion, Service Workers are an incredibly powerful tool for building offline-first web applications that deliver fast and reliable performance. By using Service Workers in an Angular application, we can cache our application's assets, provide consistent functionality, and reduce data consumption. The Angular Service Worker package makes it easy to add Service Worker support to our application, and the polling logic can be implemented to ensure updates are received reliably. Overall, Service Workers are an important technology for building modern web applications that are fast, reliable, and provide a great user experience.

Comments