Progressive Web Apps (PWAs) are web apps that aim to offer an experience similar to a native, installed application. They use service workers to cache front-end files and back-end information so they can function faster and even work offline (at least partially), add a web manifest to allow users to install the front-end on their device like any other app and even implement push notifications, all to offer an experience closer to an native app than what is expected from a “normal” website.

Angular makes it easy to fulfill the bare minimum requirements for a web app to be considered a PWA, but optimizing to deliver a truly good PWA requires a bit more work. In this article, I will try to walk you through this process.

PWA requirements

Defining a PWA just as a web app that tightens the gap between web based and native app is a bit broad. Google offers a more specific definition, providing a checklist of things they consider as minimum requirements for a web app to be considered a PWA, and also a list for those who want to implement an exemplary PWA.

There’s also a tool called Lighthouse, which is bundled with Chrome for desktop, that can help a lot in checking if your web app complies to the PWA requirements. You can access it by opening the developer tools and going to the “Audits” tab.

If you check Progressive Web App and run an audit right now most tests will probably fail.

Part of the results screen of a PWA test on Lighthouse

Don’t let that discourage you, as Angular makes it quite easy to go from that to passing most tests.

Making your Angular app a PWA

The @angular/pwa npm package performs many of the steps necessary to make your Angular app a PWA. When added to your project, it will set up a service worker, add a web manifest, add icons and a theme color, and add a tag at index.html to show some content when the JavaScript code from your app hasn’t loaded (probably either because the user has a very slow connection or because their browser can’t run Angular).

To add @angular/pwa to your project, type this on a console in your project’s folder:

ng add @angular/pwa

If you run your app with ng serve and try to audit it now, many tests will be successful, but you will probably notice it still fails tests related to service workers.

If you go to the “Application” tab of the developer tools on Google Chrome (or similar tools on other browsers) you will see a service worker running, but with an error message. That’s because ng serve doesn’t work well with service workers, and it’s necessary to build your app and run through a server to make it work.

One easy way of doing this is to use the http-server npm pack. You can use the following code on a console to build your app, install http-server and run a server with it.

ng build --prod

npm install http-server -g

http-server -p 8080 -c-1 dist/<project-name>

You can access your web app at http://127.0.0.1:8080. If you audit it now it will pass all but one test (the one about redirecting HTTP to HTTPS), but you should probably only worry about this one on your production environment. Also worth noting it passes the test about running your website with HTTPS even if it isn’t just because you are running it locally. The test actually works otherwise, and will fail if the website isn’t running with HTTPS.

Optimizations

Your Angular app now has the bare minimum to be considered a PWA, and that’s actually pretty good already. If you reload your page you will notice it loads really fast thanks to the service worker caching the front-end static files, and when you deploy your app to an environment running with HTTPS, a user will be prompted to install your app.

That doesn’t mean there isn’t room for improvement.

Theme

The more obvious improvement would be to, first of all, override the “theme” applied by @angular/pwa to your project, by replacing all icons created by different sizes of your own icons, and by picking your own color for the theme-color at /src/index.html and at /src/manifest.webmanifest.

Cache

Another not so obvious yet significant optimization is to set up caching of back-end information. To do so you will need to divide your endpoints into “data groups”, which define how they will be cached.

The most important option of a data group is probably deciding between the two available strategies, “performance” and “freshness”. Performance will use cached information whenever available, while freshness always tries to fetch information from the internet, falling back to cache when there’s no connection to the internet.

It’s also possible to control how long responses stay in cache before they are discarded, and to set a limit to how large responses can be in order to be cached.

Properly using these options may greatly improve the overall performance of your web app, but misusing them may lead to showing old data to a user without their knowledge. For some types of data speed is preferable over always having the most updated data (non-critical data that rarely changes, like, for example, the link to a user profile photo shown at the navbar), but showing old data can be really bad depending on the kind of data being handled (if you are a bank showing outdated bank transactions data, you may have problems), so be careful.

In order to set up a data group, open the ngsw-config.json file (created when the pwa pack was added to the project) and add a dataGroups key to the json object. This key should contain an array of objects, each defining a name, an array of urls and a cacheConfig object.

You can check the documentation for more details. The following is a sample of how to set up a data group:

"dataGroups": [{

"name": "api-cache",

"urls": [ "/test1", "/test2/*" ],

"cacheConfig": {

"strategy": "freshness",

"maxSize": 131072,

"maxAge": "1d",

"timeout": "15s"

}

}]

Worth noting, caching responses only works for HTTP methods that don’t (or shouldn’t) make any changes on back-end information, so POSTs, DELETEs and such methods do not have their responses cached.

Handling different versions

By default, an Angular PWA will load front-end files from cache whenever available, and if there’s an active internet connection newer versions will be downloaded for the next time the users visit your website.

That means faster loading times, which is very good, but this also means a lot of users will use old versions for a while. Depending of what kind of website you have and what kind of changes you made on theses newer versions this could not make much difference, but this could also be very, very bad.

The good news is it’s not so hard to circumvent this problem, as the SwUpdate class was made for it. It provides us with two observables to listen to when updates are made available and when an update has been applied, and two promises to manually check availability and apply updates.

Those can be used to warn the user about a newer version and allow them to either continue using the current one or to reload the page and use the newer one. You could add something like this to your app.component.ts to achieve this behavior:

constructor(swUpdate: SwUpdate) { } ngOnInit() {

this.swUpdate.available

.subscribe(update => this.newVersion = true);

} // Make a button that only appears when newVersion and use this function as its action

reload() {

this.swUpdate.activated

.subscribe(update => window.location.reload(true)); this.swUpdate.activateUpdate();

}

Modify this to let the user know there’s a new version (in this code, when newVersion is set to true). How exactly you do this depends on your web app’s design, but a dialog/ modal or a toast / snackbar is probably a good way to go.

As a heads up, SwUpdate.available takes a few seconds to fire after the page loads, and it will only fire the first time you boot the website after an update. This makes developing this functionality a bit harder, but if you keep in mind how things work there shouldn’t be any problem.

User experience

Up until now you have probably designed your web app with the mindset that if your user isn’t online then they can’t really access your web app. It would be a great idea to make sure things wouldn’t just collapse on a quick connection failure, but in the end it was meant to work with an internet connection.

Now your web app is actually meant to work both online and offline, and you must make sure the experience remains consistent all the time. Even further, you need to make sure your web app “feels” like a native, installed app.

There are many tweaks you could do to improve this. Some of these are actually good practice for pretty much every website, but are particularly important for a PWA.

Responsiveness nowadays is important for any website, but it’s extra important for one trying to pass for a native app. If your UI doesn’t feel like it was optimized for a user’s screen size it won’t feel like a native app, and it’s specially bad if they have to keep zooming in/ out to properly use it.

If your website has some real time features it’s probably a good idea to inform your users if they are offline (either by checking from time to time or by using the Network Information API on supporting browsers).

Don’t let page transitions feel like your web app is blocked. If a page transition depends on data being transmitted (like when a form is being submitted) show a loading indicator, and if it doesn’t need anything just show the next page instantly (Angular apps are single page applications, make good use of that).

If a part of a page is waiting for some data to download, make sure it won’t keep “jumping” while data is fetched. Give a fixed height for image containers and show skeleton screens (or at least some simple loading indicator) where applicable . For faster back-end services, you may want to use resolve guards to further the illusion of a native app and not a website being built as data is received.

If a request failed, show a retry component (specially important for users with poor connection, which is common for mobile users) or, if not possible for some reason, at least show an error message.

Notifications

This would be a lengthy subject and deserve an article of its own. I will at least recommend Angular University blog’s article about notifications on Angular.

Further improvements

With all of that, your Angular PWA should offer a great user experience, with good performance and a native app-like experience.

I will not cover these in detail on this article, but Google suggests even more improvements to further improve the user experience in your PWA, like making sure that your website can be indexed by search engines and that it looks good when shared on social media.

Angular apps being single page apps has its advantages, but one downside is the fact it’s harder for search engines and social networks to crawl Angular apps. That’s not to say it can’t be done, just that it’s not so easy. Look up for “Angular SEO” and you will probably find many articles on the subject. Depending on the complexity of your website you may need to look for “Angular Universal”, which implements server-side rendering on Angular apps.

Little things, like making sure an input won’t be hidden by a mobile on-screen keyboard, or implementing scroll history (when pressing back, making the scroll position the same as when the user left the page) are also a nice touch.

Lastly, be mindful of how you handle push notifications and install notifications. Make it too little and you will be losing a great user engagement opportunity, make it too much and you will annoy your users. You probably don’t want to annoy your users.