How do you add offline capabilities to a web application? If you search for an answer to these questions, the answer that will invariably pop up is to turn your application into a Progressive Web App (PWA). It is easy to find tutorials that cover the basics. But then the difficult questions arise: what architectural pattern do I use to fetch, store and cache my app’s data? How do I synchronize local changes to the data back to the server? How can I test all this?
In this three-part series, we’ll go beyond the basics of PWAs and try to answer some of those questions.
This first part describes two global strategies for dealing with data when your app needs to work offline: an online-first approach, and an offline-first approach. In addition, we will discover that “offline” is often more nuanced than simply “not having a network connection”.
In the second part, to be published in the next few weeks, we’ll look in more detail at the online-first strategy. We’ll see how we can add offline capabilities to an app that is otherwise built as a fully online app.
Then in the third part, we’ll consider the offline-first strategy. In this scenario, we discuss techniques you can use to build a true offline app. An app where “occasionally connected” is the operative term.
Online-first vs offline-first
Once you master the basics of creating a PWA and using the Service Worker, you need to decide how to use these capabilities in your own application. There is an infinite number of options here, but they can roughly be divided into two approaches: online-first and offline-first.
With an online-first approach, you would create your web application as if it were a regular, always-online web app. Then, at certain strategic points in your app, you add in offline capabilities. This comes mostly in the form of caching. You simply cache the results that your app makes to the API. Then when you happen to be offline, the app can simply load the cached results and remain operational.
This approach is arguably the easiest to implement. You simply create a regular web app, and ‘bolt on’ some bits where you need offline capabilities. For simple data caching strategies, the Angular Service Worker implementation offers most of the required functionality out of the box. However, you are quite limited when it comes to offering a rich offline experience. The only data that you have available, is data that you have retrieved and cached previously. And when you need to temporarily store outgoing data (POST/PUT calls to your backend), you need to build your own mechanism to handle this.
So how do you do this, exactly? How can Angular’s service worker implementation help? What patterns can you use to cache and store data? We’ll see some specific answers to these questions in part two.
In an offline-first approach, you will design your app with the assumption that you do not have a network connection. Some scenarios do require a connection of course, such as the initial loading of the app, or synchronizing data between your app and your backend. But every other action in your application should be designed to work offline. Any data that your app needs or produces should be fetched or pushed at controlled moments and stored in a local data store.
Using this approach, you have the most control to create a rich offline experience. Since not having a network connection is the default assumption in this approach, the app will naturally have a very powerful and rich offline experience. This does come at a cost, though. Compared to the online-first approach, it is much harder to add such capabilities to an existing (online-only) app. Common operations such as authentication, the retrieval of simple reference data, or even making available simple assets such as web fonts or external scripts, all need to be re-thought and re-built to fit the offline-first approach.
In part three, we’ll dive deeper into this approach. We will look at techniques for creating a sync mechanism and storing your data locally.
But when we talk about “offline” capabilities, it is worth examining what “offline” means, exactly. Because that is more complex than it seems.
What does “offline” really mean?
We tend to think of online/offline as a binary proposition: you’re either online or you’re offline. And in the comfort of your optical-fiber-powered multi-gigabit development environment, it’s easy to forget that there’s a third option that needs to be part of the discussion. This third option is a bad network connection.
This may not be relevant for all offline apps. But will your app run on mobile devices? And does your app depend on a cellular network for its data connection? Then, a bad network connection is a very real option. And handling a bad connection well is crucial to building a reliable offline app.
It’s easy to miss this scenario though, because the available APIs and tools don’t point it out. The Navigator browser API has a boolean property,
onLine. And the Service Workers tab in the Google Chrome Developer Tools also shows a boolean checkbox
Offline. Even the slowest throttling preset in the Network tab of Chrome’s Developer Tools still allows for some degree of network functionality, which basically means you’re still online. But when your app is used on a mobile device, in areas with bad network reception, it is very likely that your device will detect a cell tower and show a network connection. That means that your browser and app think that they are online. But the speed and latency of this connection are such that no real data transfer can take place.
Simulate a bad network connection
In part 2 of this series, we’ll look at strategies that you can build into your app to deal with a bad network connection. But in order to develop and test an app, we need a way to simulate a bad connection.
Fortunately, you can simulate a bad network connection using a custom throttling preset in Google Chrome (last time I checked, Firefox does not have such an option). The default presets all throttle the network connection to some extent, but they are usually still too fast to test your bad connection strategies. But you can easily create your own preset, using the following steps.
- In the Network tab of the developer tools, select the dropdown with the throttling presets and click Add….
This will take you to a settings screen where you can manage custom network throttling profiles.
- Click Add custom profile… to create a new network throttling profile.
This will open a small inline form where you can specify a name, the maximum upload/download speeds, and the latency of this preset.
- Enter very low upload and download speeds and set the latency to a very high value. The trick here is to set the latency higher than the timeout threshold for the app that you’re developing. Then, then you are sure to get timeout errors in your app, which is exactly what we need to test how our app deals with a bad network.
- Click Save to store your new preset.
The preset is now available in the dropdown menu with throttling presets.
When you want your app to work offline, you can turn it into a PWA. But that is only the beginning of the solution. You need the data inside your app to be available offline as well. To achieve this, you can choose between two main strategies: online-first, and offline-first.
In addition, “offline” usually isn’t the binary choice that it seems. A bad network connection fools your app into thinking it is online, but in reality, it has no usable way to transfer data at all. To develop and test for such a scenario, you can use a custom network throttling preset in the browser.
Stay tuned for part two of this series, where we’ll look at the online-first strategy!