Service for detecting, downloading, and activating app updates with comprehensive version event handling and unrecoverable state management.
Injectable service for managing Angular app updates through the service worker.
/**
* Service for subscribing to update notifications and controlling app updates
* Provides observables for version events and methods for update management
* Requires NgswCommChannel to be available via dependency injection
*/
@Injectable()
class SwUpdate {
/**
* Observable that emits version-related events including detection,
* installation failures, and readiness notifications
*/
readonly versionUpdates: Observable<VersionEvent>;
/**
* Observable that emits when the app enters an unrecoverable state
* requiring a full page reload to recover
*/
readonly unrecoverable: Observable<UnrecoverableStateEvent>;
/**
* Whether the Service Worker is enabled and update checking is supported
*/
get isEnabled(): boolean;
/**
* Check for app updates and wait for new version to be ready
* @returns Promise<boolean> - true if update found and ready, false if no update
* @throws Error with ERR_SW_NOT_SUPPORTED message if service worker not supported
*/
checkForUpdate(): Promise<boolean>;
/**
* Activate the latest downloaded version without page reload
* WARNING: Can cause version mismatches - prefer page reload instead
* @returns Promise<boolean> - true if update activated, false if none available
* @throws RuntimeError with SERVICE_WORKER_DISABLED_OR_NOT_SUPPORTED_BY_THIS_BROWSER code if service worker not supported
*/
activateUpdate(): Promise<boolean>;
}Usage Examples:
import { Component, inject } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
@Component({...})
export class UpdateComponent {
private swUpdate = inject(SwUpdate);
ngOnInit() {
if (!this.swUpdate.isEnabled) {
console.log('App updates not supported');
return;
}
// Listen for version updates
this.swUpdate.versionUpdates.subscribe(event => {
switch (event.type) {
case 'VERSION_DETECTED':
console.log('Downloading new app version:', event.version.hash);
this.showUpdateProgress();
break;
case 'VERSION_READY':
console.log('New app version ready:', event.latestVersion.hash);
this.promptForUpdate(event);
break;
case 'VERSION_INSTALLATION_FAILED':
console.error('Update failed:', event.error);
this.showUpdateError();
break;
case 'NO_NEW_VERSION_DETECTED':
console.log('App is up to date:', event.version.hash);
break;
}
});
// Listen for unrecoverable states
this.swUpdate.unrecoverable.subscribe(event => {
console.error('App in unrecoverable state:', event.reason);
if (confirm('Application update required. Reload now?')) {
document.location.reload();
}
});
}
async checkForUpdates() {
try {
const updateFound = await this.swUpdate.checkForUpdate();
if (updateFound) {
console.log('Update check: new version found');
} else {
console.log('Update check: app is current');
}
} catch (error) {
console.error('Update check failed:', error);
}
}
private promptForUpdate(event: VersionReadyEvent) {
const message = `New version available. Current: ${event.currentVersion.hash.substring(0, 8)}, Latest: ${event.latestVersion.hash.substring(0, 8)}. Update now?`;
if (confirm(message)) {
// Recommended: reload page to ensure consistency
document.location.reload();
// Alternative: activate without reload (risky)
// this.swUpdate.activateUpdate().then(() => {
// document.location.reload();
// });
}
}
}Observable that emits events related to app version changes and update lifecycle.
/**
* Observable emitting version-related events throughout update lifecycle
* Includes detection, readiness, failures, and no-update scenarios
*/
readonly versionUpdates: Observable<VersionEvent>;Version Event Types:
/**
* Event emitted when service worker detects new version on server
*/
interface VersionDetectedEvent {
type: 'VERSION_DETECTED';
version: {hash: string; appData?: object};
}
/**
* Event emitted when new version download completes and is ready
*/
interface VersionReadyEvent {
type: 'VERSION_READY';
currentVersion: {hash: string; appData?: object};
latestVersion: {hash: string; appData?: object};
}
/**
* Event emitted when version installation/download fails
*/
interface VersionInstallationFailedEvent {
type: 'VERSION_INSTALLATION_FAILED';
version: {hash: string; appData?: object};
error: string;
}
/**
* Event emitted when update check finds no new version
*/
interface NoNewVersionDetectedEvent {
type: 'NO_NEW_VERSION_DETECTED';
version: {hash: string; appData?: Object};
}
/**
* Union type of all possible version events
*/
type VersionEvent =
| VersionDetectedEvent
| VersionInstallationFailedEvent
| VersionReadyEvent
| NoNewVersionDetectedEvent;Usage Examples:
// Handle specific event types
this.swUpdate.versionUpdates.subscribe(event => {
switch (event.type) {
case 'VERSION_DETECTED':
this.showUpdateIndicator('Downloading update...');
break;
case 'VERSION_READY':
this.showUpdatePrompt(event.currentVersion, event.latestVersion);
break;
case 'VERSION_INSTALLATION_FAILED':
this.showError(`Update failed: ${event.error}`);
break;
case 'NO_NEW_VERSION_DETECTED':
this.hideUpdateIndicator();
break;
}
});
// Filter for specific events
import { filter } from 'rxjs/operators';
// Only listen for ready updates
this.swUpdate.versionUpdates.pipe(
filter(event => event.type === 'VERSION_READY')
).subscribe(event => {
this.handleUpdateReady(event as VersionReadyEvent);
});Observable that emits when the app enters a broken state requiring full reload.
/**
* Observable that emits when app enters unrecoverable state
* Indicates cached resources are inconsistent and page reload required
*/
readonly unrecoverable: Observable<UnrecoverableStateEvent>;
/**
* Event indicating app is in broken state requiring full page reload
*/
interface UnrecoverableStateEvent {
type: 'UNRECOVERABLE_STATE';
reason: string;
}Usage Examples:
// Handle unrecoverable states
this.swUpdate.unrecoverable.subscribe(event => {
console.error('Unrecoverable state:', event.reason);
// Show user-friendly message
const message = 'The application needs to reload due to an update error. Reload now?';
if (confirm(message)) {
document.location.reload();
}
});
// Automatic reload on unrecoverable state
this.swUpdate.unrecoverable.subscribe(() => {
console.warn('Auto-reloading due to unrecoverable state');
document.location.reload();
});Manually trigger update checks and wait for results.
/**
* Check for available updates and download if found
* @returns Promise resolving to true if update found and ready, false otherwise
*/
checkForUpdate(): Promise<boolean>;Usage Examples:
// Manual update check
async checkForUpdates() {
if (!this.swUpdate.isEnabled) {
console.log('Updates not supported');
return;
}
this.showLoading('Checking for updates...');
try {
const hasUpdate = await this.swUpdate.checkForUpdate();
if (hasUpdate) {
console.log('Update found - waiting for VERSION_READY event');
} else {
this.showMessage('Your app is up to date');
}
} catch (error) {
console.error('Update check failed:', error);
this.showError('Failed to check for updates');
} finally {
this.hideLoading();
}
}
// Periodic update checks
setInterval(() => {
this.swUpdate.checkForUpdate().catch(err => {
console.warn('Background update check failed:', err);
});
}, 30 * 60 * 1000); // Check every 30 minutesActivate downloaded updates without full page reload (use with caution).
/**
* Activate the latest version without page reload
* WARNING: Can cause version mismatches between app shell and lazy chunks
* @returns Promise resolving to true if activated, false if no update available
*/
activateUpdate(): Promise<boolean>;Usage Examples:
// Activate update with reload (recommended)
async activateUpdateSafely() {
try {
const activated = await this.swUpdate.activateUpdate();
if (activated) {
// Always reload after activation to ensure consistency
document.location.reload();
}
} catch (error) {
console.error('Update activation failed:', error);
}
}
// Risky: activate without reload (only for specific use cases)
async activateUpdateInPlace() {
console.warn('Activating update without reload - may cause issues');
try {
await this.swUpdate.activateUpdate();
// App continues running with potentially mismatched resources
} catch (error) {
console.error('In-place activation failed:', error);
// Fallback to full reload
document.location.reload();
}
}ngOnInit() {
if (!this.swUpdate.isEnabled) return;
// Check for updates on app start
this.swUpdate.checkForUpdate();
// Handle version events
this.swUpdate.versionUpdates.subscribe(event => {
if (event.type === 'VERSION_READY') {
// Show subtle notification
this.showUpdateBanner();
}
});
// Handle critical errors
this.swUpdate.unrecoverable.subscribe(() => {
document.location.reload();
});
}ngOnInit() {
if (!this.swUpdate.isEnabled) return;
this.swUpdate.versionUpdates.subscribe(event => {
switch (event.type) {
case 'VERSION_DETECTED':
this.updateStatus = 'Downloading update...';
break;
case 'VERSION_READY':
this.updateStatus = 'Update ready';
this.showUpdateButton = true;
break;
case 'VERSION_INSTALLATION_FAILED':
this.updateStatus = 'Update failed';
break;
}
});
}
async applyUpdate() {
this.updateStatus = 'Applying update...';
document.location.reload();
}private setupBackgroundUpdates() {
if (!this.swUpdate.isEnabled) return;
// Check every hour
interval(60 * 60 * 1000).subscribe(() => {
this.swUpdate.checkForUpdate();
});
// Show notification when ready
this.swUpdate.versionUpdates.pipe(
filter(event => event.type === 'VERSION_READY')
).subscribe(() => {
this.notificationService.show({
message: 'App update available',
action: 'Update',
onAction: () => document.location.reload()
});
});
}Updates can fail at various stages. Always handle errors gracefully:
// Comprehensive error handling
this.swUpdate.versionUpdates.subscribe(event => {
if (event.type === 'VERSION_INSTALLATION_FAILED') {
this.handleUpdateError(event.error);
}
});
this.swUpdate.unrecoverable.subscribe(event => {
this.handleUnrecoverableError(event.reason);
});
private handleUpdateError(error: string) {
console.error('Update error:', error);
// Log to error service, show user message
this.showRetryOption();
}
private handleUnrecoverableError(reason: string) {
console.error('Unrecoverable error:', reason);
// Force reload or show critical error message
this.forceReload();
}document.location.reload() after activateUpdate()unrecoverable eventsisEnabled before using update features