CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ngrx--effects

Side effect model for @ngrx/store that manages asynchronous operations and external interactions in Angular applications using RxJS

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

advanced-features.mddocs/

Advanced Features

Lifecycle hooks, error handling, and advanced effect management capabilities for sophisticated NgRx Effects usage patterns and custom effect behaviors.

Capabilities

Lifecycle Hooks

Interfaces for controlling effect registration, execution, and initialization with fine-grained control over effect behavior.

/**
 * Hook for providing unique identifiers for effect instances
 * Useful for debugging and effect instance tracking
 */
interface OnIdentifyEffects {
  /**
   * Provide unique identifier for this effect instance
   * @returns Unique string identifier
   */
  ngrxOnIdentifyEffects(): string;
}

/**
 * Hook for controlling effect lifecycle and execution
 * Allows custom handling of resolved effects before they run
 */
interface OnRunEffects {
  /**
   * Control effect execution with custom logic
   * @param resolvedEffects$ - Stream of resolved effect notifications
   * @returns Modified stream of effect notifications
   */
  ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>): Observable<EffectNotification>;
}

/**
 * Hook for dispatching custom actions after effect registration
 * Useful for initialization actions or setup logic
 */
interface OnInitEffects {
  /**
   * Return action to dispatch after effect registration
   * @returns Action to dispatch during effect initialization
   */
  ngrxOnInitEffects(): Action;
}

Usage Examples:

import { Injectable } from "@angular/core";
import { Actions, createEffect, ofType, OnIdentifyEffects, OnRunEffects, OnInitEffects } from "@ngrx/effects";

@Injectable()
export class AdvancedEffects implements OnIdentifyEffects, OnRunEffects, OnInitEffects {
  constructor(private actions$: Actions) {}

  // Provide unique identifier for debugging
  ngrxOnIdentifyEffects(): string {
    return 'AdvancedEffects-' + Math.random().toString(36).substr(2, 9);
  }

  // Control effect execution with custom logic
  ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>): Observable<EffectNotification> {
    return resolvedEffects$.pipe(
      // Add custom logging
      tap(notification => {
        console.log(`Effect ${notification.propertyName} from ${notification.sourceName} executed`);
      }),
      // Add delay for testing or rate limiting
      delay(100),
      // Filter effects based on conditions
      filter(notification => {
        // Only run certain effects in production
        if (environment.production && notification.propertyName === 'debugEffect$') {
          return false;
        }
        return true;
      })
    );
  }

  // Dispatch initialization action
  ngrxOnInitEffects(): Action {
    return AppActions.effectsInitialized({
      effectsClass: 'AdvancedEffects',
      timestamp: Date.now()
    });
  }

  // Regular effects
  loadData$ = createEffect(() =>
    this.actions$.pipe(
      ofType(AppActions.loadData),
      switchMap(() =>
        this.dataService.loadData().pipe(
          map(data => AppActions.loadDataSuccess({ data }))
        )
      )
    )
  );

  debugEffect$ = createEffect(() =>
    this.actions$.pipe(
      tap(action => console.log('Debug action:', action))
    ),
    { dispatch: false }
  );
}

// Example with initialization effect
@Injectable()
export class AuthEffects implements OnInitEffects {
  constructor(private actions$: Actions, private authService: AuthService) {}

  ngrxOnInitEffects(): Action {
    // Check authentication status on effect initialization
    const isAuthenticated = this.authService.isAuthenticated();
    return isAuthenticated 
      ? AuthActions.checkAuthSuccess()
      : AuthActions.checkAuthFailure();
  }

  // Auth-related effects...
}

Error Handling

Comprehensive error handling system with customizable error handlers and retry mechanisms.

/**
 * Type definition for custom effects error handlers
 * @param observable$ - Observable that may emit errors
 * @param errorHandler - Angular ErrorHandler service
 * @returns Observable with error handling applied
 */
type EffectsErrorHandler = <T extends Action>(
  observable$: Observable<T>,
  errorHandler: ErrorHandler
) => Observable<T>;

/**
 * Default error handling implementation with retry logic
 * @param observable$ - Observable to apply error handling to
 * @param errorHandler - Angular ErrorHandler service
 * @param retryAttemptLeft - Number of retry attempts remaining (default: 10)
 * @returns Observable with default error handling and retry logic
 */
function defaultEffectsErrorHandler<T extends Action>(
  observable$: Observable<T>,
  errorHandler: ErrorHandler,
  retryAttemptLeft: number = 10
): Observable<T>;

/** Injection token for custom error handlers */
const EFFECTS_ERROR_HANDLER: InjectionToken<EffectsErrorHandler>;

Usage Examples:

import { Injectable, ErrorHandler } from "@angular/core";
import { EFFECTS_ERROR_HANDLER, defaultEffectsErrorHandler } from "@ngrx/effects";

// Custom error handler implementation
export const customEffectsErrorHandler: EffectsErrorHandler = <T extends Action>(
  observable$: Observable<T>,
  errorHandler: ErrorHandler
) => {
  return observable$.pipe(
    catchError((error, caught) => {
      // Custom logging
      console.error('Effect error:', error);
      
      // Send to error reporting service
      errorReportingService.reportError(error);
      
      // Custom retry logic based on error type
      if (error.status === 503) {
        // Retry server unavailable errors with exponential backoff
        return caught.pipe(delay(2000));
      } else if (error.status >= 400 && error.status < 500) {
        // Don't retry client errors
        errorHandler.handleError(error);
        return EMPTY;
      } else {
        // Use default handling for other errors
        return defaultEffectsErrorHandler(caught, errorHandler, 3);
      }
    })
  );
};

// Register custom error handler
bootstrapApplication(AppComponent, {
  providers: [
    provideStore({}),
    provideEffects(AppEffects),
    {
      provide: EFFECTS_ERROR_HANDLER,
      useValue: customEffectsErrorHandler
    }
  ]
});

// Effect with custom error handling disabled
@Injectable()
export class CustomErrorEffects {
  customHandlingEffect$ = createEffect(() =>
    this.actions$.pipe(
      ofType(SomeActions.riskyAction),
      switchMap(action =>
        this.riskyService.performOperation(action.data).pipe(
          map(result => SomeActions.success({ result })),
          catchError(error => {
            // Custom error handling logic
            if (error.code === 'BUSINESS_ERROR') {
              return of(SomeActions.businessError({ error: error.message }));
            } else {
              // Log and continue
              console.error('Unexpected error:', error);
              return EMPTY;
            }
          })
        )
      )
    ),
    { useEffectsErrorHandler: false } // Disable automatic error handling
  );
}

// Error handler with different strategies per effect
export class ConditionalErrorHandler implements EffectsErrorHandler {
  constructor(private notificationService: NotificationService) {}

  <T extends Action>(
    observable$: Observable<T>,
    errorHandler: ErrorHandler
  ): Observable<T> {
    return observable$.pipe(
      catchError((error, caught) => {
        // Different handling based on error context
        if (error.effectName?.includes('critical')) {
          // Critical effects - show user notification and don't retry
          this.notificationService.showError('Critical operation failed');
          errorHandler.handleError(error);
          return EMPTY;
        } else if (error.effectName?.includes('background')) {
          // Background effects - silent retry
          return caught.pipe(delay(5000));
        } else {
          // Default handling
          return defaultEffectsErrorHandler(caught, errorHandler);
        }
      })
    );
  }
}

Effect Notification System

Internal notification system for effect execution and monitoring.

/**
 * Notification wrapper for effect outputs and metadata
 */
class EffectNotification {
  /** The effect observable */
  effect: Observable<any>;
  /** Property name of the effect in the source class */
  propertyName: PropertyKey;
  /** Name of the source class containing the effect */
  sourceName: string;
  /** Instance of the source class */
  sourceInstance: any;
  /** RxJS notification containing the effect result */
  notification: Notification<Action | null>;
}

Effects Initialization

Constants and functions for effects system initialization.

/** Action type for effects initialization */
const ROOT_EFFECTS_INIT: "@ngrx/effects/init";

/**
 * Action creator for effects initialization
 * @returns Action indicating effects system initialization
 */
function rootEffectsInit(): Action;

Usage Examples:

// Listen for effects initialization
@Injectable()
export class AppEffects {
  effectsInit$ = createEffect(() =>
    this.actions$.pipe(
      ofType(ROOT_EFFECTS_INIT),
      map(() => AppActions.systemInitialized())
    )
  );
}

// Custom initialization handling
@Injectable()
export class InitializationEffects {
  handleInit$ = createEffect(() =>
    this.actions$.pipe(
      ofType(ROOT_EFFECTS_INIT),
      withLatestFrom(this.store.select(selectAppConfig)),
      switchMap(([action, config]) => [
        AppActions.configLoaded({ config }),
        AppActions.startBackgroundTasks(),
        ...(config.enableAnalytics ? [AnalyticsActions.initialize()] : [])
      ])
    )
  );
}

Advanced Effect Patterns

Effect with State Machine:

@Injectable()
export class StateMachineEffects implements OnRunEffects {
  private state = 'idle';

  ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>): Observable<EffectNotification> {
    return resolvedEffects$.pipe(
      tap(() => {
        // Update internal state machine
        this.updateState();
      }),
      filter(() => this.canExecuteEffects())
    );
  }

  private updateState() {
    // State machine logic
    switch (this.state) {
      case 'idle':
        this.state = 'processing';
        break;
      case 'processing':
        this.state = 'completed';
        break;
    }
  }

  private canExecuteEffects(): boolean {
    return this.state !== 'blocked';
  }
}

Rate-Limited Effects:

@Injectable()
export class RateLimitedEffects implements OnRunEffects {
  private lastExecution = 0;
  private readonly rateLimitMs = 1000;

  ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>): Observable<EffectNotification> {
    return resolvedEffects$.pipe(
      filter(() => {
        const now = Date.now();
        if (now - this.lastExecution > this.rateLimitMs) {
          this.lastExecution = now;
          return true;
        }
        return false;
      })
    );
  }
}

docs

actions-filtering.md

advanced-features.md

effect-creation.md

index.md

module-setup.md

testing.md

tile.json