Offline-First Improvements
Comprehensive offline-first architecture enhancements for error-free operation
๐๏ธ Offline-First Improvements
Comprehensive offline-first architecture enhancements for error-free operation when backend is unavailable.
๐ Table of Contents
- Overview
- Problem Statement
- Solution Architecture
- Implementation Details
- Components Modified
- Usage Guide
- Testing
- Expected Outcomes
๐ฏ Overview
GoGrab Space now features a sophisticated error-free offline-first architecture that:
- โ Eliminates console errors when backend is unavailable
- โ Provides seamless offline work - users can continue working without interruption
- โ Automatically syncs when backend becomes available
- โ Offers clear UI feedback about connection state
- โ Preserves data integrity across online/offline transitions
Key Innovation
The architecture centers around a new NetworkSyncStateService in @storage-system that acts as the single source of truth for all network and backend connectivity state. All sync-related services now consume this centralized service.
๐ด Problem Statement
Before Implementation
When the backend-api was offline, multiple services would spam the console with errors:
| Service | Error Pattern | Frequency |
|---|---|---|
SpaceApiService |
ERR_CONNECTION_REFUSED | Every API call |
SyncEngineService |
Continuous retry errors | Every 5 seconds |
UserJourneyTrackerService |
Sync failures | Every 30 seconds |
RealtimeSyncService |
WebSocket connection errors | Repeatedly |
HttpMongoDBSyncAdapter |
HTTP sync errors | On every batch |
Root Cause
Services lacked awareness of network/backend state and attempted operations that would inevitably fail, resulting in:
- Console spam making debugging difficult
- Poor user experience with constant error messages
- Unnecessary network requests wasting resources
- No graceful degradation to offline mode
๐๏ธ Solution Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ @storage-system โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ NetworkSyncStateService (NEW) โ โ
โ โ โ โ
โ โ Signals: โ โ
โ โ โข isOnline - Browser network status โ โ
โ โ โข isBackendAvailable - Backend HTTP health check โ โ
โ โ โข isWebSocketConnected - Socket.IO connection state โ โ
โ โ โข connectionState - Combined state enum โ โ
โ โ โข canMakeApiCalls - Safe to call HTTP APIs โ โ
โ โ โข canUseWebSocket - Safe to use WebSocket โ โ
โ โ โข shouldSuppressErrors - Error suppression flag โ โ
โ โ โ โ
โ โ Events: โ โ
โ โ โข onBackendAvailable$ - Backend came online โ โ
โ โ โข onBackendUnavailable$ - Backend went offline โ โ
โ โ โข onReconnected$ - Reconnection completed โ โ
โ โ โข onConnectionStateChange$ - State changed โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Enhanced Components โ โ โ
โ โ โผ โ โ
โ โ โข HttpMongoDBSyncAdapter - Uses canSync() before operations โ โ
โ โ โข IntelligentSyncEngine - Backend availability aware โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
Exposes signals โ via provider
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ @libs-foundational/core-angular โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โ
โ โ SyncEngineServiceโ โRealtimeSyncServiceโ โUserJourneyTracker โ โ
โ โ โข Pauses sync โ โ โข Smart reconnect โ โ โข Skips when โ โ
โ โ โข Error suppress โ โ โข WS state track โ โ offline โ โ
โ โ โข Auto-resume โ โ โข Error suppress โ โ โข Auto-resume โ โ
โ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
โ โ SpaceApiService โ โ UserApiService โ โ
โ โ โข Cache fallback โ โ โข Silent skip โ โ
โ โ โข Network errors โ โ โข Graceful null โ โ
โ โ โ use cache โ โ returns โ โ
โ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ @libs-ui/shared-ui โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ OfflineIndicatorComponent (NEW) โ โ
โ โ โข Shows "Offline Mode" banner โ โ
โ โ โข Shows "Reconnecting..." state โ โ
โ โ โข Auto-hides when online โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Why This Architecture?
- Single Source of Truth -
@storage-systemowns all sync/network state - No Duplication - Services consume NetworkSyncStateService, don't recreate
- Clean Dependency Flow - storage-system โ core-angular (not circular)
- Centralized Error Suppression - One place to control logging behavior
๐ง Implementation Details
1. NetworkSyncStateService
Location: libs/libs-foundational/storage-system/src/sync/services/network-sync-state.service.ts
The foundation for ALL offline-first behavior.
// Connection states
export type ConnectionState =
| 'online' // Backend available, full sync
| 'offline' // Browser offline, local-only
| 'backend-unavailable' // Browser online but backend down
| 'reconnecting'; // Attempting to reconnect
// Key API
readonly isOnline = signal<boolean>(navigator.onLine);
readonly isBackendAvailable = signal<boolean>(false);
readonly isWebSocketConnected = signal<boolean>(false);
readonly connectionState = computed<ConnectionState>(() => {
if (!this._isOnline()) return 'offline';
if (this._reconnecting) return 'reconnecting';
if (!this._isBackendAvailable()) return 'backend-unavailable';
return 'online';
});
readonly canMakeApiCalls = computed(() =>
this._isOnline() && this._isBackendAvailable()
);
readonly canUseWebSocket = computed(() =>
this.canMakeApiCalls() && this.isWebSocketConnected()
);
// Events for coordinated recovery
readonly onBackendAvailable$: Observable<void>;
readonly onBackendUnavailable$: Observable<void>;
readonly onReconnected$: Observable<void>;
Health Check with Exponential Backoff
// Backoff intervals: 5s โ 10s โ 20s โ 40s โ 60s (max)
private healthCheckIntervals = [5000, 10000, 20000, 40000, 60000];
private async performHealthCheck(): Promise<boolean> {
try {
const response = await firstValueFrom(
this.http.get<{ status: string }>(this.healthCheckUrl, {
headers: { 'X-Health-Check': 'true' }
}).pipe(timeout(5000))
);
return response?.status === 'ok';
} catch {
return false;
}
}
2. Provider Configuration
Location: libs/libs-foundational/storage-system/src/sync/providers/provide-network-sync-state.ts
// In app.config.ts
provideNetworkSyncState({
apiBaseUrl: environment.backendApiUrl,
healthCheckUrl: '/api/health',
maxErrorLogs: 3, // Suppress after 3 errors
verbose: isDevMode(), // Verbose logging in dev
autoInitialize: true // Start health checks on init
})
3. Error Suppression Strategy
The service tracks error count and suppresses repetitive logging:
// Only log first N errors, then suppress
recordError(): boolean {
this._errorCount.update(c => c + 1);
return this._errorCount() <= this.config.maxErrorLogs;
}
shouldSuppressErrors(): boolean {
return this._errorCount() >= this.config.maxErrorLogs;
}
// Reset on successful connection
resetErrorCount(): void {
this._errorCount.set(0);
}
๐ฆ Components Modified
Storage System (@storage-system)
| Component | Changes |
|---|---|
NetworkSyncStateService |
NEW - Central network state management |
provideNetworkSyncState |
NEW - Angular provider with auto-init |
IntelligentSyncEngine |
Added canSync() computed, uses NetworkSyncState |
HttpMongoDBSyncAdapter |
Added network checks, error suppression |
Core Angular (@libs-foundational/core-angular)
| Component | Changes |
|---|---|
SyncEngineService |
Uses canMakeApiCalls(), auto-resume on reconnect |
SpaceApiService |
Returns cached data when offline, exposes isOfflineMode |
UserApiService |
Silent skip when offline, graceful null returns |
RealtimeSyncService |
Smart reconnection, WS state tracking |
UserJourneyTrackerService |
Skips sync when offline, auto-resume |
UI Components (@libs-ui/shared-ui)
| Component | Changes |
|---|---|
OfflineIndicatorComponent |
NEW - Visual connection status banner |
App Configuration (gograb-space)
| Component | Changes |
|---|---|
app.config.ts |
Added provideNetworkSyncState() early in providers |
๐ Usage Guide
Consuming NetworkSyncStateService
import { NetworkSyncStateService } from '@storage-system';
@Injectable({ providedIn: 'root' })
export class MyService {
private readonly networkState = inject(NetworkSyncStateService);
async doSomethingWithBackend(): Promise<void> {
// Check before making API calls
if (!this.networkState.canMakeApiCalls()) {
console.log('Backend unavailable - queuing operation');
return;
}
try {
await this.http.get('/api/data').toPromise();
} catch (error) {
// Only log if not suppressed
if (!this.networkState.shouldSuppressErrors()) {
console.error('API call failed:', error);
}
}
}
}
Listening for Reconnection Events
export class MySyncService {
private readonly networkState = inject(NetworkSyncStateService);
private readonly destroyRef = inject(DestroyRef);
constructor() {
// Trigger sync when backend becomes available
this.networkState.onBackendAvailable$.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {
console.log('Backend available - syncing now');
this.networkState.resetErrorCount();
this.performSync();
});
}
}
Using the Offline Indicator Component
<!-- Add to app.component.html or layout -->
<ux-offline-indicator></ux-offline-indicator>
<!-- Rest of your app -->
<router-outlet></router-outlet>
import { OfflineIndicatorComponent } from '@shared-ui';
@Component({
standalone: true,
imports: [OfflineIndicatorComponent, RouterOutlet],
template: `
<ux-offline-indicator></ux-offline-indicator>
<router-outlet></router-outlet>
`
})
export class AppComponent {}
Exposing Connection State in UI
@Component({
template: `
@if (isOffline()) {
<div class="offline-notice">
Working offline - changes saved locally
</div>
}
`
})
export class MyComponent {
private networkState = inject(NetworkSyncStateService);
isOffline = computed(() => !this.networkState.canMakeApiCalls());
}
๐งช Testing
Manual Testing Checklist
Start app with backend offline
- No console errors on startup
- Offline indicator shows
- App loads and is usable
Work offline
- Can navigate between pages
- Can modify data (saved locally)
- No repeated error messages
Start backend while app running
- Backend detected within 60 seconds
- Sync automatically resumes
- Offline indicator disappears
Kill backend while working
- Graceful handling (no crash)
- Offline indicator appears
- Work can continue
Simulating Offline Mode
// In browser console:
// Simulate offline
window.__networkState__.setBackendUnavailable();
// Simulate online
window.__networkState__.setBackendAvailable();
โ Expected Outcomes
| Metric | Before | After |
|---|---|---|
| Console errors when offline | 100+ per minute | 0 |
| User experience | Confusing errors | Clear feedback |
| Data loss risk | Possible | None (local storage) |
| Recovery time | Manual refresh | Automatic |
| Backend load | Unnecessary retries | Smart polling |
Connection State Flow
Browser Online + Backend Up = "online" (full functionality)
โ
โผ Backend goes down
โ
Browser Online + Backend Down = "backend-unavailable"
โ (local storage works,
โ no sync attempts)
โ
โผ Health check succeeds
โ
"reconnecting" (brief state)
โ
โผ Sync completes
โ
"online" (back to normal)
๐ File Reference
New Files Created
libs/libs-foundational/storage-system/src/sync/services/network-sync-state.service.tslibs/libs-foundational/storage-system/src/sync/providers/provide-network-sync-state.tslibs/libs-ui/shared-ui/src/lib/components/offline-indicator/offline-indicator.component.tslibs/libs-ui/shared-ui/src/lib/components/offline-indicator/index.ts
Files Modified
libs/libs-foundational/storage-system/src/sync/services/index.tslibs/libs-foundational/storage-system/src/sync/providers/index.tslibs/libs-foundational/storage-system/src/sync/services/intelligent-sync-engine.tslibs/libs-foundational/storage-system/src/sync/adapters/http-mongodb-sync-adapter.tslibs/libs-foundational/core-angular/src/lib/services/sync-engine.service.tslibs/libs-foundational/core-angular/src/lib/services/space-api.service.tslibs/libs-foundational/core-angular/src/lib/services/user-api.service.tslibs/libs-foundational/core-angular/src/lib/services/realtime-sync.service.tslibs/libs-foundational/core-angular/src/lib/modules/user-journey-tracker/user-journey-tracker.service.tslibs/libs-ui/shared-ui/src/lib/components/index.tsapps/gograb-space/src/app/app.config.ts
๐ฎ Future Enhancements
- Offline Data Queue Visualization - Show users pending changes
- Conflict Resolution UI - When local and server data conflict
- Background Sync API - Use Service Worker for more robust sync
- Selective Sync - Allow users to choose what syncs when
- Bandwidth-Aware Sync - Adjust sync based on connection quality
๐ Related Documentation
Last Updated: December 2025
Implementation Status: โ
Complete
Session: 7