Implementing Offline Capabilities
1. Detecting Network Status
| Property | Description | Values |
|---|---|---|
| navigator.onLine | Indicates if the browser is online or offline | true / false |
Example: Check Network Status
if (navigator.onLine) {
console.log("Online");
} else {
console.log("Offline");
}
Note:
navigator.onLine is a best-effort indicator and may not reflect actual network availability.
Network Status Detection Methods
navigator.onLine- Basic online/offline status- HTTP requests - Actual network connectivity check
- Service Worker - Intercept and handle offline scenarios
2. Implementing Offline Storage
Example: Using SQLite for Local Data Persistence
import { SQLiteDB } from '@ionic-native/sqlite';
const db = await SQLiteDB.create({
name: 'mydb.db',
location: 'default'
});
await db.executeSql('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)');
await db.executeSql('INSERT INTO users (name) VALUES ('John Doe')');
| Feature | Description | Support |
|---|---|---|
| SQLite | Local SQL database for structured data | NEW Ionic 7+ |
| Local Storage | Simple key-value storage using browser's localStorage | Native support |
| IndexedDB | Advanced client-side storage for large amounts of data | EXPERIMENTAL |
Note: For hybrid apps, SQLite is preferred over localStorage for complex data structures.
Warning: IndexedDB is not supported in all browsers and may require polyfills.
Storage Options Comparison
- SQLite: Best for structured relational data, supports transactions
- Local Storage: Simple key-value pairs, limited to 5MB
- IndexedDB: Large-scale data storage, complex queries
3. Caching API Responses
| Property | Description | Default | Notes |
|---|---|---|---|
| cacheKey | Unique identifier for the cached response | null |
Required for caching to work |
| ttl | Time to live in milliseconds | 300000 |
5 minutes by default |
| strategy | Cache strategy to use | 'network-first' |
Options: 'network-first', 'cache-first', 'stale-while-revalidate' |
Example: Caching API Response with Ionic
import { HttpClient } from '@angular/common/http';
import { CacheService } from 'ionic-cache';
constructor(private http: HttpClient, private cache: CacheService) {}
getCachedData() {
return this.cache.get('api-data', () => {
return this.http.get('https://api.example.com/data');
}, {
ttl: 600000, // 10 minutes
strategy: 'cache-first'
});
}
Note: The
ionic-cache library provides a simple way to cache API responses in Ionic apps. It supports multiple caching strategies and can be easily integrated into existing Angular services.
4. Implementing Data Sync
Example: Basic Data Sync Setup
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class DataService {
private apiUrl = 'https://api.example.com/data';
constructor(private http: HttpClient) {}
async syncData() {
const localData = await this.getLocalData();
const serverData = await this.http.get(this.apiUrl).toPromise();
// Simple diff and sync logic
const changes = this.calculateChanges(localData, serverData);
await this.applyChanges(changes);
return { success: true };
}
private async getLocalData() {
// Fetch from local storage or database
return JSON.parse(localStorage.getItem('localData') || '[]');
}
private calculateChanges(local, server) {
// Logic to detect differences
return { added: [], updated: [], deleted: [] };
}
private async applyChanges(changes) {
// Apply changes to local storage or database
localStorage.setItem('localData', JSON.stringify(changes));
}
}
| Feature | Description | Status |
|---|---|---|
| syncStrategy | Defines how data is synchronized (e.g., push, pull, bidirectional) | NEW |
| conflictResolution | How to handle data conflicts (e.g., last-write-wins, manual) | DEPRECATED |
| syncInterval | How often to sync data (in milliseconds) | NEW |
Sync Workflow
- Check network status
- Fetch local changes
- Compare with server data
- Resolve conflicts if any
- Push local changes to server
- Update local storage with server data
Data Sync Strategies
- Push Only: Local changes are sent to server, no pull
- Pull Only: Server data is fetched, local changes ignored
- Bidirectional: Both push and pull with conflict handling
Note: Use
Network plugin to detect offline/online status in Ionic apps.
Warning: Always implement conflict resolution to prevent data loss.
5. Handling Offline Forms
| Property | Description | Default | Notes |
|---|---|---|---|
| formId | Unique identifier for the form | null |
Required for tracking offline submissions |
| offlineStorageKey | Storage key for local persistence | 'ionic-offline-forms' |
Used to store form data in IndexedDB or localStorage |
| autoSaveInterval | Time (in ms) between auto-saves | 5000 |
Set to 0 to disable auto-save |
Example: Basic Offline Form Setup
import { IonicForm } from '@ionic/offline';
const form = new IonicForm({
formId: 'user-profile',
offlineStorageKey: 'user-data',
autoSaveInterval: 3000
});
form.onSave((data) => {
console.log('Saved offline data:', data);
});
Example: Submitting Offline Form Data
async function submitForm(formId) {
const form = new IonicForm({ formId });
const data = await form.getOfflineData();
if (navigator.onLine) {
// Submit to server
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(data)
});
await form.clearOfflineData();
} else {
// Store for later
await form.saveOffline(data);
}
}
Note: Ensure that form data is properly serialized and validated before storing offline.
Warning: Do not store sensitive data in local storage without encryption.
Key Considerations
- Implement proper data validation before saving offline
- Use unique
formIdto prevent data conflicts - Handle network state changes gracefully
- Clear offline data after successful submission
6. Using Offline-First Architecture
Offline-first architecture ensures that your Ionic application functions seamlessly even when network connectivity is lost. This approach prioritizes local data storage and synchronization, making your app resilient to network interruptions.
| Feature | Description | Implementation |
|---|---|---|
| Local Data Storage | Stores data locally using IndexedDB or SQLite for offline access. | const db = new Dexie('myAppDB'); |
| Sync Strategy | Manages data synchronization when the device reconnects to the network. | syncData(); |
| Cache Management | Handles caching of API responses to reduce network requests. | cache.get('endpoint'); |
Example: Implementing Local Storage with Dexie.js
import Dexie from 'dexie';
class MyDatabase extends Dexie {
constructor() {
super('MyAppDB');
this.version(1).stores({
items: '++id, name, description'
});
}
}
const db = new MyDatabase();
// Add item to local storage
db.items.add({ name: 'Item 1', description: 'Description of item 1' });
// Retrieve items
db.items.toArray().then(items => {
console.log(items);
});
Note: Dexie.js is a popular library for IndexedDB management in web applications.
Offline-First Workflow
- Application loads and initializes local storage
- User interacts with the app while offline
- Changes are stored locally in IndexedDB or SQLite
- When network is restored, sync process begins
- Data is synchronized with the backend server
7. Implementing Conflict Resolution
| Conflict Type | Description | Solution Approach | Tools/Methods |
|---|---|---|---|
| Data Overwrite Conflicts | Multiple users edit the same data, leading to one update overwriting another. | Implement version control or timestamps to detect and resolve updates. | etag, lastModified |
| Merge Conflicts | Changes from different sources need to be merged, often in a database or file system. | Use merge strategies like 3-way merge or conflict markers for manual review. | git merge, merge strategies |
| Offline Sync Conflicts | Changes made offline are synced when connectivity is restored, causing conflicts. | Queue changes and apply conflict resolution logic during sync. | ionic offline, sync queue |
Example: Handling Data Overwrite Conflicts
const handleOverwriteConflict = (localData, serverData) => {
if (localData.version < serverData.version) {
// Server version is newer, apply it
return serverData;
} else if (localData.version > serverData.version) {
// Local version is newer, push it to server
return localData;
} else {
// Versions match, no conflict
return null;
}
};
Example: Using ETags for Conflict Detection
const fetchWithEtag = async (url, etag) => {
const response = await fetch(url, {
headers: { 'If-None-Match': etag }
});
if (response.status === 304) {
// No changes, use cached data
return { changed: false };
} else {
const data = await response.json();
return { changed: true, data, etag: response.headers.get('ETag') };
}
};
Note: ETags are a simple yet effective way to detect changes on the server side without transferring full data.
Warning: Manual conflict resolution can lead to data loss if not handled carefully. Always log conflicts for review.
8. Caching Static Assets
| Property | Description | Default | Notes |
|---|---|---|---|
| cache | Enables caching of static assets | false |
Set to true to enable caching |
| maxAge | Maximum age of cached assets in milliseconds | 86400000 |
1 day default cache duration |
| strategy | Cache strategy to use (e.g., cache-first, network-first) |
cache-first |
Choose appropriate strategy for asset type |
Example: Configure Static Asset Caching
const config = {
cache: true,
maxAge: 86400000,
strategy: 'cache-first'
};
// Apply to Ionic app build process
Note: Caching static assets improves load times and reduces network usage for repeat visits.
Warning: Ensure cache invalidation is handled properly to avoid serving stale content.
9. Displaying Offline UI Indicators
Example: Basic Offline Indicator
import { Network } from '@capacitor/network';
async function checkNetworkStatus() {
const status = await Network.getStatus();
const offlineIndicator = document.getElementById('offline-indicator');
if (!status.connected) {
offlineIndicator.style.display = 'block';
} else {
offlineIndicator.style.display = 'none';
}
}
Network.addListener('networkStatusChange', checkNetworkStatus);
| Property | Description | Values |
|---|---|---|
| connected | Indicates if the device is connected to the internet | true or false |
| connectionType | Type of network connection (wifi, cellular, etc.) | wifi, cellular, none |
Note: The
Network plugin requires the @capacitor/network package to be installed.
Example: Offline Indicator with Toast Notification
import { Network } from '@capacitor/network';
import { Toast } from '@capacitor/toast';
async function showOfflineToast() {
const status = await Network.getStatus();
if (!status.connected) {
await Toast.show({
message: 'You are offline. Some features may not work.',
duration: 'long'
});
}
}
Workflow: Implementing Offline UI
- Use
Network.getStatus()to check connection status - Display a visual indicator (e.g., banner, toast) when offline
- Hide the indicator when connection is restored
- Update UI elements to reflect offline state (e.g., disable actions)
10. Testing Offline Behavior
Example: Mocking Network Conditions
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { Network } from '@ionic-native/network/ngx';
describe('Network Service', () => {
let network: Network;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [Network]
});
network = TestBed.inject(Network);
});
it('should detect offline state', () => {
spyOn(network, 'onDisconnect').and.returnValue(Promise.resolve());
expect(network.onDisconnect).toHaveBeenCalled();
});
});
| Method | Description | Return Type |
|---|---|---|
| onDisconnect() | Triggers when device goes offline | Promise<void> |
| onConnect() | Triggers when device reconnects | Promise<void> |
Note: Use
Network plugin to simulate offline conditions in tests.