Side effect model for @ngrx/store that manages asynchronous operations and external interactions in Angular applications using RxJS
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Core functionality for creating and managing effects in NgRx applications, supporting both class-based and functional patterns with comprehensive configuration options.
Creates effects from source functions with configuration options, supporting both class-based and functional patterns.
/**
* Creates an effect from a source function with optional configuration
* @param source - Function returning an Observable
* @param config - Optional effect configuration
* @returns Effect with metadata for registration
*/
function createEffect<
C extends EffectConfig & { functional?: false },
DT extends DispatchType<C>,
OTP,
R extends EffectResult<OT>,
OT extends ObservableType<DT, OTP>
>(
source: () => R & ConditionallyDisallowActionCreator<DT, R>,
config?: C
): R & CreateEffectMetadata;
function createEffect<Source extends () => Observable<unknown>>(
source: Source,
config: EffectConfig & { functional: true; dispatch: false }
): FunctionalEffect<Source>;
function createEffect<Source extends () => Observable<Action>>(
source: Source & ConditionallyDisallowActionCreator<true, ReturnType<Source>>,
config: EffectConfig & { functional: true; dispatch?: true }
): FunctionalEffect<Source>;
function createEffect<
Result extends EffectResult<unknown>,
Source extends () => Result
>(
source: Source,
config?: EffectConfig
): (Source | Result) & CreateEffectMetadata;
type FunctionalEffect<
Source extends () => Observable<unknown> = () => Observable<unknown>
> = Source & FunctionalCreateEffectMetadata;
interface FunctionalCreateEffectMetadata extends CreateEffectMetadata {
'__@ngrx/effects_create__': EffectConfig & { functional: true };
}
interface CreateEffectMetadata {
'__@ngrx/effects_create__': EffectConfig;
}Usage Examples:
import { Injectable, inject } from "@angular/core";
import { createEffect, Actions, ofType } from "@ngrx/effects";
import { map, switchMap, catchError } from "rxjs/operators";
import { of } from "rxjs";
// Class-based effect
@Injectable()
export class BookEffects {
constructor(private actions$: Actions) {}
loadBooks$ = createEffect(() =>
this.actions$.pipe(
ofType(BookActions.loadBooks),
switchMap(() =>
this.bookService.getBooks().pipe(
map(books => BookActions.loadBooksSuccess({ books })),
catchError(error => of(BookActions.loadBooksFailure({ error })))
)
)
)
);
// Non-dispatching effect
logBookActions$ = createEffect(() =>
this.actions$.pipe(
ofType(BookActions.loadBooksSuccess),
map(action => console.log('Books loaded:', action.books))
),
{ dispatch: false }
);
}
// Functional effect
export const loadBooksEffect = createEffect(
(actions$ = inject(Actions), bookService = inject(BookService)) =>
actions$.pipe(
ofType(BookActions.loadBooks),
switchMap(() =>
bookService.getBooks().pipe(
map(books => BookActions.loadBooksSuccess({ books })),
catchError(error => of(BookActions.loadBooksFailure({ error })))
)
)
),
{ functional: true }
);
// Effect with custom error handling disabled
export const customErrorHandlingEffect = createEffect(
() => actions$.pipe(
ofType(SomeActions.customAction),
switchMap(() =>
someService.riskyOperation().pipe(
map(result => SomeActions.success({ result })),
// Custom error handling
catchError(error => {
console.error('Custom error handling:', error);
return of(SomeActions.failure({ error }));
})
)
)
),
{ useEffectsErrorHandler: false }
);Configuration interface for customizing effect behavior.
interface EffectConfig {
/** Whether the effect should dispatch actions (default: true) */
dispatch?: boolean;
/** Whether this is a functional effect (default: false) */
functional?: boolean;
/** Whether to use the effects error handler (default: true) */
useEffectsErrorHandler?: boolean;
}Configuration Options:
dispatch: When false, the effect won't dispatch actions to the store (useful for side effects like logging, navigation, etc.)functional: Must be true for functional effects created outside of classesuseEffectsErrorHandler: When false, disables automatic error handling, allowing custom error handling strategiesMetadata interface attached to created effects for runtime processing.
interface CreateEffectMetadata {
/** Internal marker for NgRx effects system */
'__@ngrx/effects_create__': EffectConfig;
}
/**
* Extract effect metadata from effect instances
* @param instance - Effect instance to extract metadata from
* @returns Array of effect metadata
*/
function getEffectsMetadata<T>(instance: T): Array<{
propertyName: string;
dispatch: boolean;
useEffectsErrorHandler: boolean;
}>;Conditional Effects:
loadUserData$ = createEffect(() =>
this.actions$.pipe(
ofType(UserActions.loadUser),
switchMap(action =>
this.authService.isAuthenticated$.pipe(
switchMap(isAuthenticated =>
isAuthenticated
? this.userService.getUser(action.userId).pipe(
map(user => UserActions.loadUserSuccess({ user }))
)
: of(UserActions.loadUserFailure({ error: 'Not authenticated' }))
)
)
)
)
);Debounced Search Effect:
search$ = createEffect(() =>
this.actions$.pipe(
ofType(SearchActions.search),
debounceTime(300),
distinctUntilChanged(),
switchMap(action =>
this.searchService.search(action.query).pipe(
map(results => SearchActions.searchSuccess({ results })),
catchError(error => of(SearchActions.searchFailure({ error })))
)
)
)
);Non-dispatching Effect for Side Effects:
saveToLocalStorage$ = createEffect(() =>
this.actions$.pipe(
ofType(PreferencesActions.updatePreferences),
tap(action => {
localStorage.setItem('preferences', JSON.stringify(action.preferences));
})
),
{ dispatch: false }
);