Creating Progressive Web Apps with Angular

Progressive web applications are the new standard in this modern era of web development. They’re pure web applications built with the very web technologies (HTML, JS & CSS) but behave as a hybrid. i.e. somewhere between web and native apps.

The concept of PWAs started booming in late 2015 and is now becoming a standard for modern web apps as companies both large and small are now adopting PWAs for their current and future projects/products. The list includes Twitter Lite & Tinder, among others. You can see some cool examples of PWAs at PWA Rocks.

One important thing to know before targeting PWAs for production is that Apple currently does not support the major features of PWA such as Service Workers, Push Notifications & Installing to home screen through a browser provided prompt. You can read more about this in this informative blog post.

What is a PWA?

According to Google, PWAs should be:

  1. Reliable — They should load instantly even if the network conditions are not so good or if there’s no network at all.
  2. Fast — They should be highly interactive and provide fast user experience. I.e. smooth scrolling and animations etc.
  3. Engaging — They should be engaging so the user gets an immersive experience and stays connected with the app.

Apart from these characteristics, PWAs are usually responsive and should adopt to most devices and screen sizes. They’re installable on mobile devices, meaning you can have a link on your home screen to launch the app right away making it easier to access. And they work in all web browsers. In modern browsers, we’ll leverage the amazing APIs we’ll talk about later, but in older browsers, our app should still work perfectly fine, without the amazing modern stuff of course.

Let’s get started.

I already have a simple web application which searches books using the Google Books API. We will convert this into a Progressive Web App. The finalized PWA can be cloned from this code repo. I suggest you fork the repository, clone into your machine and checkout the non-pwa-app branch to follow along with the tutorial.

The final application should look like this and can be viewed here:

The steps going forward assume that you have Git, NodeJS, and Angular CLI installed. You will also need http-server package for this tutorial.

Running the application (non-pwa)

From the root of your project, execute the command ng-serve using terminal/ cmd and navigate to localhost:4200. You should see the app running as:

Non-pwa app served using `ng-serve`

We can always test the performance of our PWA using Lighthouse and improve areas of our application lacking in performance. Here are the stats of our non-pwa:

Over all stats of non-pwa-app
The load time on an average low connection is > 11 seconds

Making the app reliable & faster

We want our application to be reliable. It should work even if the network is poor or is unavailable. Let’s see what happens if we turn off the network and run our application. To do this, open the inspector. Go to the network tab and click on Offline. Do not close the inspector.

Now, refresh the page and you will see something like this:

To avoid this happening to end users, we’ll be using Service Workers to cache our application. Then, we can use it even when there’s no network.

To achieve this, we can configure the service worker manually. I recommend using the amazing pwa tools that the Angular team has provided. We’re going to install those tools now to introduce a service worker in our application.
Now, close the chrome inspector. From your project root, execute the following command which installs the required plugins via npm/yarn in our project:

npm install --save @angular/service-worker @angular/platform-server ng-pwa-tools


yarn add @angular/service-worker @angular/platform-server ng-pwa-tools --save

Now we’ll enable service workers inside our application. By default, they’re turned off in angular-cli when creating a project. We can enable them by executing the following command from project root which updates our angular-cli.json:

ng set apps.0.serviceWorker=true

From now on, we’ll be using the bash script to serve our application because Service Workers don’t work in a development environment. We’ll be creating prod builds and serving the dist folder using http-server package. The script now should look like this:

PATH=$PATH:$(npm bin)
set -x
# Production build
ng build --prod
# Serve
cd dist

The next step is to let the service worker know about our application’s routing & assets so the service worker can cache those for repeated visits. We could create a service worker manifest for this purpose but since we already have installed some amazing tools, we’re going to create this manifest automatically by letting the tool know about our application’s routing.
We’re going to add the command below in our to do that:

./node_modules/.bin/ngu-sw-manifest --module src/app/app.module.ts

This creates the routing information in the sw-manifest.json that is being created and since our app’s entry point is app.module.ts it traverses the whole app to gain that information. So our looks like this now.

PATH=$PATH:$(npm bin)
set -x
# Production build
ng build --prod
# merge ngsw-manifest and copy to dist
./node_modules/.bin/ngu-sw-manifest --module src/app/app.module.ts \
--out dist/ngsw-manifest.json
# Serve
cd dist

You can execute the bash script using sh on mac and on windows you can use cmder or git-bash to execute the command. After that, navigate to localhost:8080 to see the app running in prod mode.
What actually happened is that it created the prod build in the dist folder and served to localhost:8080, created a file sw-manifest.json and copied it to the dist folder , included a file worker-basic.min.js which is a basic Service Worker on page load, a service worker registration script which registers this basic service worker on page load.

Below are the contents of ngsw-manifest.json :

Contents of auto-generated ngsw-manifest.json

The contents include our application’s routing as well as all the static files that can be cached for our applications. Notice the long hash against the file URLs. This hash informs the service worker when the files have been updated by us and the service worker now needs to re-cache the newer files.

Now, go to localhost:8080 and open chrome inspector, go to Application tab and you should see our service worker up and running:

Since our service worker is alive now, switch to the Network tab, and refresh the page. You should see something like this:

Assets being fetched from the service worker now

We’re actually caching our assets now using the service worker. Amazing, right? But this doesn’t load any of the data that we require. That’s because currently we are not caching our search API calls. But we should get some results even if we are offline.
To do that, we’ll add our own custom ngsw-manifest.json inside the src folder in our project. The PWA tools we’re using will combine the auto-generated ngsw-manifest.json with our custom json file and put the output into the dist folder. So let’s create the json file and put the following content inside it:

Contents of our custom ngsw-manifest.json

The Google Books API calls are configured under the dynamic chunk as we want every request prefixing the URL to be cached. We can fine tune the caching technique for every one of our URL configurations for performance (check in service worker first, if not found, fetch from server) or for freshness (check from the server first, if not found/offline, fetch from the service worker).

We can also cache external resources like external CSS, fonts, js files that we might include from some CDN etc.

Now, if you run sh, go to the Network tab in the inspector, and then refresh your page. You will see that the service API call (to Google Books API), is cached too and the data is being received from the server:

Books Api Call fetching books list from the service worker
Books Api Call fetching book details from the service worker

Tada! The application is now faster and more reliable because on every repeated visit, it fetches content from the service worker saving network calls for the end-user. Try refreshing the browser a few times and see how fast the data loads providing a smooth, faster and immersive experience. Also, try the offline mode to be further amazed 😉 All the previous visits should be cached.

We have improved our app, so let’s see if the performance seems different in Lighthouse.

Performance gain after implementing service worker
First page view time has decreased but is still 11.3s

As you can see, we have made some progress.
In part two, we’ll be looking at how to give users the best application load experience, even with browsers that are slow to load the first screen.