or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cli-tools.mdconfiguration.mdindex.mdlow-level-communication.mdmodule-setup.mdpush-notifications.mdtesting.mdupdate-management.md
tile.json

update-management.mddocs/

App Updates and Version Management

Service for detecting, downloading, and activating app updates with comprehensive version event handling and unrecoverable state management.

Capabilities

SwUpdate Service

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();
      // });
    }
  }
}

Version Event Observable

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);
});

Unrecoverable State Observable

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();
});

Update Checking

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 minutes

Update Activation

Activate 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();
  }
}

Common Patterns

Automatic Update Flow

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();
  });
}

User-Controlled Updates

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();
}

Background Update with Notification

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()
    });
  });
}

Error Handling

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();
}

Best Practices

  1. Always reload after activation: Use document.location.reload() after activateUpdate()
  2. Handle unrecoverable states: Always subscribe to unrecoverable events
  3. Graceful degradation: Check isEnabled before using update features
  4. User-friendly messaging: Convert technical events to understandable messages
  5. Error recovery: Provide retry mechanisms for failed updates
  6. Background checks: Implement periodic update checking for long-running apps