You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular项目实现PWA离线数据处理:Service Worker与IndexedDB集成求助

Angular Offline Support with Service Worker + IndexedDB + MongoDB Sync

Hey there! I’ve built similar offline-first Angular apps before, so let me walk you through a practical, step-by-step approach to get this working smoothly.

1. Start with Angular's Native Service Worker

Angular has excellent built-in support for Service Workers via @angular/service-worker — this is the easiest way to enable core offline capabilities like caching static assets (JS, CSS, images) and intercepting network requests.

Setup Steps:

  • Install the package:
    ng add @angular/service-worker
    
  • Enable Service Worker in angular.json: Ensure the serviceWorker flag is set to true in your production build configuration.
  • Configure ngsw-config.json: This file controls caching behavior. For your use case:
    • Add static assets (like fonts, images) to the assetGroups section for offline access.
    • Define dataGroups if you want to cache read-only API responses (e.g., product listings) for offline viewing.
  • Register the Service Worker in app.module.ts:
    import { ServiceWorkerModule } from '@angular/service-worker';
    import { environment } from '../environments/environment';
    
    @NgModule({
      imports: [
        // ... other imports
        ServiceWorkerModule.register('ngsw-worker.js', {
          enabled: environment.production,
          registrationStrategy: 'registerWhenStable:30000'
        })
      ]
    })
    export class AppModule { }
    

2. Integrate IndexedDB with Dexie.js

Native IndexedDB is low-level and verbose — Dexie.js is a lightweight, Angular-friendly wrapper that simplifies working with IndexedDB. It’s perfect for storing offline user data that needs to sync later.

Setup Dexie:

  • Install the packages:
    npm install dexie dexie-angular
    
  • Create a dedicated database service (e.g., offline-db.service.ts):
    import { Injectable } from '@angular/core';
    import { Dexie } from 'dexie';
    import { HttpClient } from '@angular/common/http';
    
    interface PendingSyncItem {
      id?: number;
      requestDetails: {
        url: string;
        method: string;
        body: any;
      };
      operation: 'create' | 'update' | 'delete';
      timestamp: number;
    }
    
    @Injectable({ providedIn: 'root' })
    export class OfflineDbService extends Dexie {
      pendingSync: Dexie.Table<PendingSyncItem, number>;
    
      constructor(private http: HttpClient) {
        super('OfflineAppDB');
        this.version(1).stores({
          pendingSync: '++id, operation, timestamp' // Define your table schema
        });
        this.pendingSync = this.table('pendingSync');
    
        // Listen for online event to trigger sync
        window.addEventListener('online', () => this.syncPendingData());
      }
    
      // Add request to pending sync queue
      async addPendingSync(request: any, operation: 'create' | 'update' | 'delete') {
        await this.pendingSync.add({
          requestDetails: request,
          operation,
          timestamp: Date.now()
        });
      }
    
      // Fetch all pending sync items
      async getPendingSyncItems() {
        return await this.pendingSync.orderBy('timestamp').toArray();
      }
    
      // Delete item after successful sync
      async deletePendingSyncItem(id: number) {
        await this.pendingSync.delete(id);
      }
    
      // Sync pending data to MongoDB
      async syncPendingData() {
        const pendingItems = await this.getPendingSyncItems();
        
        for (const item of pendingItems) {
          try {
            // Replay the original request
            const { url, method, body } = item.requestDetails;
            switch (method) {
              case 'POST':
                await this.http.post(url, body).toPromise();
                break;
              case 'PUT':
                await this.http.put(url, body).toPromise();
                break;
              case 'DELETE':
                await this.http.delete(url).toPromise();
                break;
            }
            // Remove item from queue after success
            await this.deletePendingSyncItem(item.id!);
          } catch (error) {
            console.error('Failed to sync item:', error);
            break; // Stop syncing to avoid cascading failures
          }
        }
      }
    }
    

3. Capture Offline Requests with an HTTP Interceptor

Use an Angular HTTP Interceptor to detect offline status. When offline, store the request in IndexedDB instead of sending it to the server. When online, the sync logic above will handle replaying requests.

Create the Interceptor:

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { OfflineDbService } from './offline-db.service';

@Injectable()
export class OfflineInterceptor implements HttpInterceptor {

  constructor(private offlineDb: OfflineDbService) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    // Skip GET requests (cache these via Service Worker instead)
    if (request.method !== 'GET' && !navigator.onLine) {
      const operation = request.method === 'POST' ? 'create' : 
                        request.method === 'PUT' ? 'update' : 'delete';
      
      // Store request details in IndexedDB
      this.offlineDb.addPendingSync({
        url: request.url,
        method: request.method,
        body: request.body
      }, operation);

      // Return a fake success response to keep the UI responsive
      return of({ type: 'offline-stored' } as HttpEvent<unknown>);
    }

    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        // Handle failed requests due to offline
        if (error.status === 0 && !navigator.onLine) {
          const operation = request.method === 'POST' ? 'create' : 
                            request.method === 'PUT' ? 'update' : 'delete';
          this.offlineDb.addPendingSync({
            url: request.url,
            method: request.method,
            body: request.body
          }, operation);
        }
        return throwError(() => error);
      })
    );
  }
}

Register the interceptor in app.module.ts:

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { OfflineInterceptor } from './offline.interceptor';

@NgModule({
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: OfflineInterceptor, multi: true }
  ]
})
export class AppModule { }

4. Add Manual Sync (Optional)

Give users control to trigger sync manually with a button in your UI:

// In your component
constructor(private offlineDb: OfflineDbService) {}

triggerManualSync() {
  this.offlineDb.syncPendingData();
  // Add a toast or notification to confirm sync started
}

5. Testing & Edge Cases

  • Simulate Offline Mode: Use Chrome DevTools > Application > Service Workers > Check "Offline" to test.
  • Conflict Resolution: Implement checks (e.g., timestamps or version numbers in your data models) to handle cases where server data changed while the user was offline.
  • Service Worker Updates: Angular’s SW automatically handles updates, but you can add a prompt to notify users when a new app version is available.

内容的提问来源于stack exchange,提问作者VinoPravin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:34:10