Error Handling Patterns

Comprehensive error handling patterns for building resilient Aurelia applications with graceful degradation and user-friendly feedback.

These patterns show you how to handle errors gracefully across different layers of your Aurelia application - from API calls and routing to form validation and global error boundaries.

1. Global error boundary with user feedback

Goal: Catch unhandled errors anywhere in your app, log them for debugging, and show users a friendly message instead of a blank screen.

Steps

  1. Create an error boundary service that tracks errors and provides recovery options:

    import { ILogger, resolve } from '@aurelia/kernel';
    import { observable } from '@aurelia/runtime';
    
    export interface AppError {
      message: string;
      stack?: string;
      timestamp: number;
      context?: string;
    }
    
    export class ErrorBoundaryService {
      @observable currentError: AppError | null = null;
      private errors: AppError[] = [];
      private logger = resolve(ILogger);
    
      captureError(error: Error, context?: string) {
        const appError: AppError = {
          message: error.message,
          stack: error.stack,
          timestamp: Date.now(),
          context
        };
    
        this.errors.push(appError);
        this.currentError = appError;
    
        this.logger.error(`[${context || 'Unknown'}] ${error.message}`, error);
    
        return appError;
      }
    
      clearError() {
        this.currentError = null;
      }
    
      getRecentErrors(count: number = 10) {
        return this.errors.slice(-count);
      }
    }
  2. Register a global error handler during app startup:

    import { Aurelia } from 'aurelia';
    import { ErrorBoundaryService } from './error-boundary-service';
    
    export async function main() {
      const au = Aurelia.app(MyApp);
      const errorBoundary = au.container.get(ErrorBoundaryService);
    
      window.addEventListener('error', (event) => {
        errorBoundary.captureError(event.error, 'Window');
        event.preventDefault();
      });
    
      window.addEventListener('unhandledrejection', (event) => {
        errorBoundary.captureError(
          new Error(event.reason?.message || 'Unhandled promise rejection'),
          'Promise'
        );
        event.preventDefault();
      });
    
      await au.start();
    }
  3. Display errors in your root component with recovery actions:

    import { resolve } from '@aurelia/kernel';
    import { ErrorBoundaryService } from './error-boundary-service';
    
    export class MyApp {
      private errorBoundary = resolve(ErrorBoundaryService);
    
      reload() {
        window.location.reload();
      }
    
      dismissError() {
        this.errorBoundary.clearError();
      }
    }
    <div class="error-banner" if.bind="errorBoundary.currentError">
      <div class="error-content">
        <h3>Something went wrong</h3>
        <p>${errorBoundary.currentError.message}</p>
        <div class="error-actions">
          <button click.trigger="dismissError()">Dismiss</button>
          <button click.trigger="reload()">Reload Page</button>
        </div>
      </div>
    </div>
    
    <au-viewport></au-viewport>

Checklist

  • Unhandled errors display in the banner instead of crashing the app

  • Console logs include full error details for debugging

  • Users can dismiss transient errors or reload for critical failures

  • Error history is available for support tickets

2. API error handling with retry and fallback

Goal: Handle API failures gracefully with automatic retries, fallback data, and clear user feedback about network issues.

Steps

  1. Create an API service wrapper with error handling:

  2. Use the service in components with loading and error states:

  3. Display appropriate UI for each state:

Checklist

  • Failed API calls retry automatically with exponential backoff

  • Components show loading state during fetch operations

  • Users see clear error messages when data fails to load

  • Fallback data prevents empty states from crashing the UI

  • Users can manually retry failed operations

3. Router navigation error handling

Goal: Handle route navigation failures, missing routes, and guard rejections with appropriate redirects and user feedback.

Steps

  1. Create a navigation error handler:

  2. Configure router with error handling:

  3. Create an error page with helpful actions:

Checklist

  • Failed navigation redirects to error page instead of blank screen

  • Authentication failures redirect to login with return URL

  • Users can retry failed navigation or return home

  • 404 routes show custom not-found page

  • Navigation errors are logged for debugging

4. Form validation errors with field-level feedback

Goal: Display validation errors inline next to form fields while preventing submission of invalid data.

Steps

  1. Create a form with validation rules and error display:

  2. Display validation errors inline with accessible markup:

Checklist

  • Validation errors appear inline next to each field

  • Users see errors on blur and as they type to fix them

  • Form submission is prevented when validation fails

  • Server-side errors merge with client-side validation

  • Submit button is disabled during submission

  • Error messages are accessible with ARIA attributes

5. Async operation error boundaries with timeout

Goal: Wrap async operations with timeouts and error handling to prevent indefinite loading states.

Steps

  1. Create a timeout utility with error handling:

  2. Use the timeout wrapper in components:

Checklist

  • Long-running operations timeout with clear error messages

  • Timeout errors are distinguishable from other failures

  • Users aren't stuck in infinite loading states

  • Errors are logged for monitoring

6. Optimistic updates with rollback on error

Goal: Update UI immediately for better UX, but revert changes if the server rejects the update.

Steps

  1. Implement optimistic updates with error recovery:

  2. Provide visual feedback during updates:

Checklist

  • UI updates immediately when users interact

  • Failed updates revert to previous state

  • Users see error notifications for failed operations

  • Original state is preserved for rollback

Error handling cheat sheet

Scenario
Pattern
Key Components

Global unhandled errors

Error boundary service + window event listeners

ErrorBoundaryService, window.addEventListener

API failures

Retry with exponential backoff

fetchWithErrorHandling, retry counter, delay

Navigation errors

Route guards + error pages

canLoad, loading, error routes

Form validation

Inline field errors + submit prevention

ValidationController, validation-errors

Async timeouts

Promise.race with timeout

withTimeout, TimeoutError

Optimistic updates

State snapshot + rollback

Save original state, try/catch with restore

Best practices

  1. Always show user feedback: Never fail silently - users should know when something goes wrong

  2. Provide recovery actions: Include "Retry", "Go Home", or "Dismiss" buttons

  3. Log errors for debugging: Use ErrorBoundaryService or similar to track errors

  4. Use appropriate error granularity: Global errors for critical failures, inline errors for forms

  5. Test error paths: Don't just test happy paths - simulate failures and verify recovery

  6. Avoid error cascades: Handle errors at the appropriate level to prevent bubbling

  7. Use loading states: Show when async operations are in progress

  8. Set reasonable timeouts: Don't let users wait indefinitely

  9. Make errors actionable: Tell users what they can do to fix the problem

See also

Last updated

Was this helpful?