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

actions-filtering.mddocs/

Actions and Filtering

Action stream management and type-safe action filtering capabilities for NgRx Effects, enabling precise control over which actions trigger specific effects.

Capabilities

Actions Service

Injectable service that provides access to the application's action stream, extending RxJS Observable with custom operators.

/**
 * Injectable service providing access to all dispatched actions
 * Extends Observable<Action> with additional NgRx-specific functionality
 */
class Actions<V = Action> extends Observable<V> {
  /**
   * Custom lift method for applying operators with NgRx context
   * @param operator - RxJS operator to apply
   * @returns Observable with applied operator
   */
  lift<R>(operator?: Operator<V, R>): Observable<R>;
}

Usage Examples:

import { Injectable } from "@angular/core";
import { Actions } from "@ngrx/effects";

@Injectable()
export class MyEffects {
  constructor(private actions$: Actions) {
    // actions$ is an Observable<Action> containing all dispatched actions
    this.actions$.subscribe(action => {
      console.log('Action dispatched:', action);
    });
  }
}

// In functional effects
export const myEffect = createEffect(
  (actions$ = inject(Actions)) =>
    actions$.pipe(
      // Process all actions
      tap(action => console.log('All actions:', action.type))
    ),
  { functional: true, dispatch: false }
);

ofType Operator

RxJS operator for filtering actions by type with full type safety and automatic type narrowing.

/**
 * Filters actions by string type with type narrowing
 * @param allowedTypes - Action type strings to filter by
 * @returns OperatorFunction that filters and narrows action types
 */
function ofType<E extends Extract<A, { type: T }>, A extends Action = Action, T extends string = A['type']>(
  ...allowedTypes: [T, ...T[]]
): OperatorFunction<A, E>;

/**
 * Filters actions by ActionCreator with automatic type inference
 * @param allowedTypes - ActionCreator instances to filter by
 * @returns OperatorFunction that filters to specific action types
 */
function ofType<AC extends ActionCreator<string, Creator>, U extends Action = Action>(
  ...allowedTypes: [AC, ...AC[]]
): OperatorFunction<U, ReturnType<AC>>;

Usage Examples:

import { ofType } from "@ngrx/effects";
import { createAction, props } from "@ngrx/store";

// Define actions
const loadBooks = createAction('[Book] Load Books');
const loadBooksSuccess = createAction(
  '[Book] Load Books Success',
  props<{ books: Book[] }>()
);
const loadBooksFailure = createAction(
  '[Book] Load Books Failure',
  props<{ error: string }>()
);

// Filter by ActionCreator (recommended)
loadBooks$ = createEffect(() =>
  this.actions$.pipe(
    ofType(loadBooks), // Type is automatically narrowed to loadBooks action
    switchMap(() =>
      this.bookService.getBooks().pipe(
        map(books => loadBooksSuccess({ books })),
        catchError(error => of(loadBooksFailure({ error: error.message })))
      )
    )
  )
);

// Filter by multiple ActionCreators
bookActions$ = createEffect(() =>
  this.actions$.pipe(
    ofType(loadBooksSuccess, loadBooksFailure), // Union type
    tap(action => {
      if (action.type === loadBooksSuccess.type) {
        console.log('Books loaded:', action.books); // Type-safe access
      } else {
        console.log('Load failed:', action.error); // Type-safe access
      }
    })
  ),
  { dispatch: false }
);

// Filter by string types (legacy approach)
legacyEffect$ = createEffect(() =>
  this.actions$.pipe(
    ofType('[User] Load User', '[User] Update User'),
    // Actions are typed as Action with specified types
    map(action => {
      // Manual type checking required
      if (action.type === '[User] Load User') {
        // Handle load user
      }
      return someOtherAction();
    })
  )
);

Advanced Filtering Patterns

Conditional Action Processing:

conditionalEffect$ = createEffect(() =>
  this.actions$.pipe(
    ofType(UserActions.updateUser),
    filter(action => action.user.isActive), // Additional filtering
    switchMap(action =>
      this.userService.updateUser(action.user).pipe(
        map(() => UserActions.updateUserSuccess())
      )
    )
  )
);

Multiple Action Types with Different Handling:

multiActionEffect$ = createEffect(() =>
  this.actions$.pipe(
    ofType(
      DataActions.loadData,
      DataActions.refreshData,
      DataActions.reloadData
    ),
    switchMap(action => {
      // Different logic based on action type
      const force = action.type === DataActions.reloadData.type;
      
      return this.dataService.getData({ force }).pipe(
        map(data => DataActions.loadDataSuccess({ data })),
        catchError(error => of(DataActions.loadDataFailure({ error })))
      );
    })
  )
);

Action Filtering with State Dependency:

stateAwareEffect$ = createEffect(() =>
  this.actions$.pipe(
    ofType(AppActions.someAction),
    withLatestFrom(this.store.select(selectCurrentUser)),
    filter(([action, user]) => user != null), // Only process if user exists
    map(([action, user]) => AppActions.processWithUser({ action, user }))
  )
);

Debounced Action Processing:

debouncedSearchEffect$ = createEffect(() =>
  this.actions$.pipe(
    ofType(SearchActions.searchQuery),
    debounceTime(300), // Wait 300ms between actions
    distinctUntilChanged((prev, curr) => prev.query === curr.query),
    switchMap(action =>
      this.searchService.search(action.query).pipe(
        map(results => SearchActions.searchSuccess({ results }))
      )
    )
  )
);

Core Types

interface Action {
  type: string;
}

interface ActionCreator<T extends string = string, C extends CreatorFunction<any> = CreatorFunction<any>> {
  readonly type: T;
  (...args: Parameters<C>): Action & ReturnType<C>;
}

type CreatorFunction<P> = (...args: any[]) => P;

interface Operator<T, R> {
  (source: Observable<T>): Observable<R>;
}

type OperatorFunction<T, R> = (source: Observable<T>) => Observable<R>;

docs

actions-filtering.md

advanced-features.md

effect-creation.md

index.md

module-setup.md

testing.md

tile.json