Skip to main content

Angular Offline Support

One of the most powerful features of Progressive Web Applications (PWAs) is their ability to function offline or with poor network connectivity. In this tutorial, we'll explore how to implement offline support in Angular applications using the built-in Angular service worker.

Introduction to Offline Support

Offline support allows your application to:

  • Continue functioning when users lose internet connectivity
  • Load faster by serving cached resources
  • Provide a seamless user experience regardless of network conditions
  • Reduce server load and bandwidth consumption

By the end of this tutorial, you'll understand how to implement these capabilities in your Angular applications.

Prerequisites

Before starting, make sure you have:

  • Basic knowledge of Angular
  • Node.js and npm installed
  • Angular CLI installed (npm install -g @angular/cli)

Setting Up Angular Service Worker

Step 1: Create or Open an Angular Project

If you're starting from scratch, create a new project:

bash
ng new offline-angular-app
cd offline-angular-app

Step 2: Add PWA Support

Add the Angular service worker package to your project:

bash
ng add @angular/pwa

This command performs several important tasks:

  • Adds the @angular/service-worker package to your project
  • Enables service worker support in the Angular CLI
  • Creates a default service worker configuration file (ngsw-config.json)
  • Updates your app.module.ts to import and register the service worker
  • Creates icons for your PWA
  • Updates your index.html with a web app manifest and meta tags

Let's examine what was added to your app.module.ts:

typescript
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';

@NgModule({
imports: [
// ... other imports
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production,
// Register the ServiceWorker as soon as the application is stable
// or after 30 seconds (whichever comes first)
registrationStrategy: 'registerWhenStable:30000'
})
],
// ... rest of module configuration
})
export class AppModule { }

Understanding the Service Worker Configuration

The ngsw-config.json file controls how your application behaves offline. Let's look at its default structure:

json
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
]
}
}
]
}

This configuration defines two asset groups:

  1. App: Core application files that are prefetched and cached when the service worker is installed. These are essential files needed to run your application.

  2. Assets: Non-critical files like images and fonts that are cached lazily (when they're first requested) but updated eagerly.

Implementing Basic Offline Support

Let's build a simple application that demonstrates offline capabilities.

Create a Component for Offline Detection

First, let's create a component that shows the current network status:

bash
ng generate component network-status

Update the network-status.component.ts:

typescript
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'app-network-status',
template: `
<div [ngClass]="{'online': isOnline, 'offline': !isOnline}">
Network Status: <strong>{{ isOnline ? 'Online' : 'Offline' }}</strong>
</div>
`,
styles: [`
.online {
padding: 10px;
background-color: #dff0d8;
color: #3c763d;
border-radius: 4px;
margin: 10px 0;
}
.offline {
padding: 10px;
background-color: #f2dede;
color: #a94442;
border-radius: 4px;
margin: 10px 0;
}
`]
})
export class NetworkStatusComponent implements OnInit {
isOnline = navigator.onLine;

constructor() {}

ngOnInit(): void {
this.setupNetworkListeners();
}

setupNetworkListeners() {
window.addEventListener('online', () => {
this.isOnline = true;
});

window.addEventListener('offline', () => {
this.isOnline = false;
});
}
}

Handling Service Worker Updates

Add a component to notify users when updates are available:

bash
ng generate component update-notification

Update update-notification.component.ts:

typescript
import { Component, OnInit } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';

@Component({
selector: 'app-update-notification',
template: `
<div *ngIf="updateAvailable" class="update-notification">
A new version of this app is available.
<button (click)="updateApp()">Update Now</button>
</div>
`,
styles: [`
.update-notification {
position: fixed;
bottom: 20px;
right: 20px;
background: #3f51b5;
color: white;
padding: 15px;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
z-index: 1000;
}
button {
margin-left: 10px;
padding: 5px 10px;
background: white;
color: #3f51b5;
border: none;
border-radius: 4px;
cursor: pointer;
}
`]
})
export class UpdateNotificationComponent implements OnInit {
updateAvailable = false;

constructor(private swUpdate: SwUpdate) {}

ngOnInit(): void {
if (this.swUpdate.isEnabled) {
this.swUpdate.available.subscribe(() => {
this.updateAvailable = true;
});
}
}

updateApp() {
this.swUpdate.activateUpdate().then(() => document.location.reload());
}
}

Add Components to App Component

Update app.component.html to include these components:

html
<div class="container">
<h1>Angular Offline Support Demo</h1>

<app-network-status></app-network-status>
<app-update-notification></app-update-notification>

<div class="content">
<p>Try turning off your network connection to see how the application behaves offline!</p>

<p>Content loaded from cache will still be available even when you're offline.</p>
</div>
</div>

Caching API Responses for Offline Use

A crucial aspect of offline support is caching API responses. Let's implement this:

Step 1: Update Service Worker Configuration

Modify ngsw-config.json to include a data group for API caching:

json
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
// ... existing asset groups
],
"dataGroups": [
{
"name": "api-cache",
"urls": [
"/api/posts",
"/api/users"
],
"cacheConfig": {
"strategy": "freshness",
"maxSize": 100,
"maxAge": "1h",
"timeout": "10s"
}
}
]
}

This configuration defines:

  • URLs to cache: API endpoints that should be cached
  • Strategy: "freshness" (network-first) or "performance" (cache-first)
  • maxSize: Maximum number of responses to store
  • maxAge: How long responses remain valid
  • timeout: How long to wait for network before using cache

Step 2: Create a Service for Data Fetching

bash
ng generate service services/data

Implement the service with offline-aware data fetching:

typescript
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

@Injectable({
providedIn: 'root'
})
export class DataService {
private localStorageKey = 'offline_data_backup';
private apiUrl = 'https://jsonplaceholder.typicode.com/posts';

constructor(private http: HttpClient) {}

getPosts(): Observable<any[]> {
return this.http.get<any[]>(this.apiUrl).pipe(
tap(data => {
// Save data to localStorage as backup for complete offline scenarios
localStorage.setItem(this.localStorageKey, JSON.stringify(data));
}),
catchError(() => {
// If request fails, try to load from localStorage
const cachedData = localStorage.getItem(this.localStorageKey);
if (cachedData) {
return of(JSON.parse(cachedData));
}
return of([]);
})
);
}
}

Step 3: Create a Component to Display Posts

bash
ng generate component posts

Update posts.component.ts:

typescript
import { Component, OnInit } from '@angular/core';
import { DataService } from '../services/data.service';

@Component({
selector: 'app-posts',
template: `
<div class="posts-container">
<h2>Posts</h2>
<div *ngIf="loading">Loading posts...</div>
<div *ngIf="error" class="error">
{{ error }}
</div>
<div class="posts-list">
<div *ngFor="let post of posts" class="post-card">
<h3>{{ post.title }}</h3>
<p>{{ post.body }}</p>
</div>
</div>
</div>
`,
styles: [`
.posts-container {
margin: 20px 0;
}
.post-card {
background: #f9f9f9;
margin-bottom: 15px;
padding: 15px;
border-radius: 4px;
border-left: 4px solid #3f51b5;
}
.error {
color: red;
padding: 10px;
background: #ffeeee;
border-radius: 4px;
}
`]
})
export class PostsComponent implements OnInit {
posts: any[] = [];
loading = true;
error: string | null = null;

constructor(private dataService: DataService) {}

ngOnInit(): void {
this.fetchPosts();
}

fetchPosts() {
this.dataService.getPosts().subscribe(
data => {
this.posts = data.slice(0, 10); // Just showing first 10 posts
this.loading = false;
},
error => {
this.error = 'Failed to load posts. You might be offline.';
this.loading = false;
}
);
}
}

Add the posts component to your app component:

html
<div class="container">
<!-- Existing content -->

<app-posts></app-posts>
</div>

Testing Offline Support

To test your application's offline capabilities:

  1. Build the application for production:

    bash
    ng build --prod
  2. Serve the built application:

    bash
    npx http-server -p 8080 -c-1 dist/offline-angular-app
  3. Open the application in a browser at http://localhost:8080

  4. Use your browser's developer tools to:

    • Check that the service worker is registered (Application tab > Service Workers)
    • View cached resources (Application tab > Cache Storage)
    • Test offline mode (Network tab > Offline)

Advanced Offline Features

Custom Offline Page

Create a component for an offline fallback:

bash
ng generate component offline-fallback

Update offline-fallback.component.ts:

typescript
import { Component } from '@angular/core';

@Component({
selector: 'app-offline-fallback',
template: `
<div class="offline-container">
<h2>You're Offline</h2>
<p>The page you're trying to access can't be loaded while you're offline.</p>
<p>Please check your connection and try again.</p>
</div>
`,
styles: [`
.offline-container {
text-align: center;
padding: 30px;
background: #f8f8f8;
border-radius: 8px;
margin: 20px 0;
}
h2 {
color: #3f51b5;
}
`]
})
export class OfflineFallbackComponent {}

Handle Offline State in Your App

Create an offline service to centralize offline state management:

bash
ng generate service services/offline

Update offline.service.ts:

typescript
import { Injectable } from '@angular/core';
import { BehaviorSubject, fromEvent, Observable, merge } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({
providedIn: 'root'
})
export class OfflineService {
private online$ = new BehaviorSubject<boolean>(navigator.onLine);

constructor() {
// Listen to online/offline events and update the BehaviorSubject
merge(
fromEvent(window, 'online').pipe(map(() => true)),
fromEvent(window, 'offline').pipe(map(() => false))
).subscribe(online => this.online$.next(online));
}

get isOnline(): boolean {
return this.online$.getValue();
}

get onlineStatus$(): Observable<boolean> {
return this.online$.asObservable();
}

// Method to check if data should be fetched from network or cache
shouldUseOfflineData(): boolean {
return !this.isOnline;
}
}

Best Practices for Offline-First Applications

  1. Progressive Enhancement: Design your app to work without JavaScript or service workers first, then enhance with offline capabilities.

  2. Regular Updates: Implement a strategy for updating cached content regularly to prevent stale data.

  3. Clear Offline Indicators: Always show users when they are working offline to set expectations.

  4. Sync on Reconnect: Queue user actions performed while offline and sync them when connectivity returns.

  5. Versioned APIs: Ensure your API versioning strategy accounts for cached responses.

  6. Cache Size Management: Be mindful of cache size limitations and implement strategies to manage large data sets.

  7. Background Sync: Use background sync APIs for tasks that can be deferred until connectivity is restored.

Example background sync implementation:

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

@Injectable({
providedIn: 'root'
})
export class SyncService {
private readonly SYNC_QUEUE_KEY = 'offline_sync_queue';

constructor(private swUpdate: SwUpdate) {}

addToSyncQueue(action: any): void {
const queue = this.getSyncQueue();
queue.push({
...action,
timestamp: new Date().getTime()
});
localStorage.setItem(this.SYNC_QUEUE_KEY, JSON.stringify(queue));
}

getSyncQueue(): any[] {
const queueJson = localStorage.getItem(this.SYNC_QUEUE_KEY);
return queueJson ? JSON.parse(queueJson) : [];
}

processQueue(): void {
const queue = this.getSyncQueue();
if (queue.length === 0) return;

// Process each queued item
// This is a simplified example - you would normally process each item
// and remove it from the queue only after successful processing

console.log('Processing sync queue:', queue);
localStorage.setItem(this.SYNC_QUEUE_KEY, JSON.stringify([]));
}

setupSync(): void {
// Process queue when coming online
window.addEventListener('online', () => {
this.processQueue();
});
}
}

Summary

In this tutorial, we've covered:

  • Setting up Angular service workers for offline support
  • Configuring caching strategies for assets and API responses
  • Implementing offline detection and notifications
  • Building offline-friendly data services
  • Handling application updates
  • Best practices for offline-first development

Implementing comprehensive offline support significantly improves the user experience of your Angular applications, making them more resilient and reliable even in challenging network environments.

Further Learning Resources

Exercises

  1. Basic: Modify the ngsw-config.json file to cache additional asset types specific to your application.

  2. Intermediate: Implement a "force refresh" button that bypasses the cache and gets fresh data from the network.

  3. Advanced: Create an offline queue system that stores user actions (like form submissions) performed while offline and processes them when connectivity is restored.

  4. Expert: Build a complete offline-first application with data synchronization between client and server, handling conflict resolution when the same data is modified offline by different users.

Happy coding!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)