We sometimes deliver web applications to users whose access to constant internet connectivity is not guaranteed. Therefore, it is important to create applications that provide a seamless user experience, whether online or offline.
Angular offers a range of tools and techniques to achieve an offline-first approach that ensures your application remains functional and responsive, even with no internet connection. By leveraging Progressive Web App (PWA) capabilities, service workers, and efficient caching strategies, Angular enables developers to build apps that work offline and sync data effortlessly when connectivity is restored.
This article will explore how to create an Angular PWA that intelligently switches between online and offline modes using different techniques, ensuring a smooth user experience. You can follow the source code for this project here.
Angular online vs. Offline modes
In the online mode, Angular applications use internet connectivity to provide real-time data and dynamic content. This mode enables the application to communicate with servers, retrieve fresh data, and update the user interface in real time. When a user’s internet connectivity is lost, Angular applications can be served offline by relying on cached data and local storage mechanisms.
Angular ships with service worker implementation designed to provide a smooth user experience over a slow or unreliable network connection. Service workers work in the background and can be configured to ensure all key resources such as HTML, CSS, JavaScript files, and even API responses, are cached locally on the user’s device.
Getting started with Angular service workers
Adding a service worker to your Angular project is a step in making your app a PWA. To set up the Angular service worker, run the CLI command below:
ng add @angular/pwa
The command does the following:
- Adds the
@angular/service-worker
package, enabling service worker integration - Adds a service worker provider as a root provider in the
app.config
file - Adds a service worker build configuration, which points to the
ngsw-config.json
file - Creates a service worker configuration file,
ngsw-config.json
. This file specifies the caching behaviors of your app’s resources, assets, and data - Adds icon files for various platforms and devices to support the installed PWA on the user’s device
- Creates a
manifest.webmanifest
file that provides information about your application such as the name, icon, theme, and the app’s behavior when installed - Adds a link to the
manifest.webmanifest
file in the rootindex.html
file and also adds a meta tag for the app’s theme color
Configuring Angular service workers for caching
The ngsw-config.json
file specifies the resources and data URLs that need caching. The configuration properties that can be added to this file include:
index
: Specifies the file that serves as the root index pageassetGroups
: Specifies versioned resources that are part of the application that should be cached. These resources are defined by various properties such asinstallMode
, which determines how they are initially cached. The generatedngsw-config.json
file comes with a boilerplateassetGroups
configurationdataGroups
: Specifies the data requests that should be cached and the policy in which they are cached. Each data group is defined by properties such ascacheConfig
, which include the strategy and maximum age of the cached data
The official Angular documentation includes a list of all properties that can be configured on the ngsw-config.json
file.
In this tutorial, we’ll build a simple application that displays a list of posts from the JSONPlaceholder API. On top of the boilerplate assets and resources cache generated by the CLI command, we’ll add the dataGroups
property to cache data from the API as shown below. Here we set the URL to be cached, cache strategy, maximum size and age, and the network timeout:
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"dataGroups": [
{
"name": "api-performance",
"urls": [
"https://jsonplaceholder.typicode.com/posts"
],
"cacheConfig": {
"maxSize": 100,
"maxAge": "1d",
"timeout": "10s",
"strategy": "performance"
}
},
{
"name": "api-freshness",
"urls": [
"https://jsonplaceholder.typicode.com/posts"
],
"cacheConfig": {
"maxSize": 100,
"maxAge": "1d",
"timeout": "10s",
"strategy": "freshness"
}
}
],
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/**/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
]
}
}
]
}
To run our application, we’ll first install the http-server
package:
npm install --global http-server
Next, we’ll run the build command at the root of our application:
ng build
We’ll then serve our application, which now makes use of the Angular service worker:
http-server -p 8080 dist/
<angular-app-name>/browser
Finally, open an incognito browser and visit the application on port 8080. This helps test fresh installs. On the initial load, we have the data fetch done via the internet as shown here:
Opening the Application
tab, you can see the service worker enabled and the cached assets and data on Cache storage
:
Now, switch the network connectivity of your app to offline and refresh the page:
On page refresh, the page loads but the data fetch is done from the cache by the service worker. This means that data fetched when the user was online is cached and the recent cache is served to the user when offline:
Detecting a user’s online or offline status
Detecting the network connectivity of a user is essential when delivering a seamless experience in both online and offline modes. This can be achieved through different techniques such as the basic browser API or packages like ng-connection-service
.
Angular online/offline detection using browser APIs
The DOM API offers a navigator
interface that can be used to tell the state and identity of the browser. It has an online
property that indicates whether the browser is online. You can use event listeners to listen to changes in network status.
Let’s create a network service that monitors the online status of the user’s device:
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class NetworkService {
private onlineStatus = new BehaviorSubject
<boolean>(navigator.onLine);
public onlineStatus$: Observable<boolean> = this.onlineStatus.asObservable();
constructor() {
window.addEventListener('online', () => this.updateOnlineStatus(true));
window.addEventListener('offline', () => this.updateOnlineStatus(false));
}
private updateOnlineStatus(isOnline: boolean) {
this.onlineStatus.next(isOnline);
}
}
The code snippet above does the following:
- Creates a
BehaviorSubject
,onlineStatus
, initialized using thenavigator.online
property, which returnstrue
if a user is online andfalse
if offline - Creates an observable
onlineStatus$
, which can be subscribed to get the online/offline status - Registers two event listeners,
online
andoffline
, which trigger and call theupdateOnlineStatus
method with true or false when online or offline respectively - Defines the
updateOnlineStatus
method, which updatesonlineStatus
with the current internet connectivity state
The NetworkService
provides a real-time stream of the user’s online or offline state, which can then be subscribed to by components in the application.
In your component file, update the code as shown below:
import { Subscription } from 'rxjs';
private subscription: Subscription | undefined;
constructor(
private networkService: NetworkService,
private snackBar: MatSnackBar
) {}
ngOnInit(): void {
this.subscription = this.networkService.onlineStatus$.subscribe({
next: (isOnline) =
> {
if (isOnline) {
this.snackBar.open('You are now online', 'OK', {
duration: 3000,
verticalPosition: 'top',
});
} else {
this.snackBar.open('You are offline', 'OK', {
duration: 3000,
verticalPosition: 'top',
});
}
},
});
}
ngOnDestroy() {
this.subscription?.unsubscribe();
}
This creates a Subscription
variable and injects the NetworkService
and MatSnackBar
services. In the ngOnInit
lifecycle Hook, we subscribe to the onlineStatus$
observable from the network service. This observable emits the online status when there is a change in connectivity.
We then display a snack bar to notify users whether they are offline or online. isOnline
will be true if the user is connected to the internet and false
if they lose connectivity.
Running the commands below builds your application with recent changes and serves it on the specified port:
ng build
http-server -p 8080 dist/
<angular-app-name>/browser
Viewing your app on port 8080
and toggling the offline status, the snack bar is displayed to notify users of their offline or online status:
Angular online/offline detection using ng-connection-service
The ng-connection-service
package is used to monitor active internet connection reactively. It detects whether the browser has an active internet connection and whether your API server is running.
To install the package, run the command below:
yarn add ng-connection-service
Then, update your application to use the connection service as shown here:
import { ConnectionService, ConnectionServiceModule, ConnectionState } from 'ng-connection-service';
import { Subscription ,tap} from 'rxjs';
@Component({
...
imports: [..., ConnectionServiceModule],
...
})
private subscription: Subscription = new Subscription();
constructor(
...
private connectionService: ConnectionService
) {}
this.subscription.add(
this.connectionService.monitor().pipe(
tap((newState: ConnectionState) =
> {
if (newState.hasNetworkConnection) {
this.snackBar.open('You are now online', 'OK', {
duration: 3000,
verticalPosition: 'top',
});
} else {
this.snackBar.open('You are offline', 'OK', {
duration: 3000,
verticalPosition: 'top',
});
}
})
).subscribe()
);
First, we include the ConnectionServiceModule
in the component’s imports, then inject the ConnectionService
to our component to monitor the connection status.
Next, we create a new subscription and add the connectionService.monitor
observable to it. The observer monitors the network and internet status and returns the current ConnectionState
. The ConnectionState
has two Boolean properties:
hasNetworkConnection
: Set totrue
if the browser has an internet connection determined by the “online” or “offline” window eventshasInternetAccess
: Set totrue
if the browser has internet access determined by a heartbeat system that periodically requests a heartbeat URL. The heartbeat URL must be reliable
Finally, we use the hasNetworkConnection
property to display a snack bar. If true, we display an online status and if false, we display an offline status:
You can pass the ConnectionServiceOptions
to the monitor observable to check the update of the hasInternetAccess
property as shown below. This helps detect if a connection exists by periodically sending an HTTP request to the specified URL:
const options: ConnectionServiceOptions = {
enableHeartbeat: true,
heartbeatUrl: '
<valid URL>',
heartbeatInterval: 2000,
requestMethod:"head"
}
...this.connectionService.monitor(options)...
The ng-connection-service
service is more reliable than the DOM API as the package handles intermittent and unstable networks by sending HTTP requests at intervals to check the user’s internet access.
Conclusion
This tutorial explored building offline and online apps within Angular using Angular service workers. We discussed the Angular service worker configurations needed to cache assets, resources, and data URLs. We also reviewed how to detect online and offline status using basic browser APIs and the ng-connection-service
package.
With Angular service workers, you can build an application that works smoothly even when a user loses connectivity. The complete code for this project can be found on GitHub.
Happy coding!