+
-The `EffectsModule.forRoot()` method must be added to your `AppModule` imports even if you don't register any root-level effects.
+Effects start running **immediately** after instantiation to ensure they are listening for all relevant actions as soon as possible.
+Services used in root-level effects are **not** recommended to be used with services that are used with the `APP_INITIALIZER` token.
-### Using the Standalone API
-
-Registering effects can also be done using the standalone APIs if you are bootstrapping an Angular application using standalone features.
-
import { bootstrapApplication } from '@angular/platform-browser';
import { provideStore } from '@ngrx/store';
@@ -273,35 +265,15 @@ bootstrapApplication(AppComponent, {
});
+Feature-level effects are registered in the `providers` array of the route config.
+The same `provideEffects()` method is used to register effects for a feature.
+
-Effects start running **immediately** after instantiation to ensure they are listening for all relevant actions as soon as possible. Services used in root-level effects are **not** recommended to be used with services that are used with the `APP_INITIALIZER` token.
+Registering an effects class multiple times (for example in different lazy loaded features) does not cause the effects to run multiple times.
-## Registering Feature Effects
-
-For feature modules, register your effects by adding the `EffectsModule.forFeature()` method in the `imports` array of your `NgModule`.
-
-
-import { NgModule } from '@angular/core';
-import { EffectsModule } from '@ngrx/effects';
-
-import { MoviesEffects } from './effects/movies.effects';
-import * as actorsEffects from './effects/actors.effects';
-
-@NgModule({
- imports: [
- EffectsModule.forFeature(MoviesEffects, actorsEffects)
- ],
-})
-export class MovieModule {}
-
-
-### Using the Standalone API
-
-Feature-level effects are registered in the `providers` array of the route config. The same `provideEffects()` function is used in root-level and feature-level effects.
-
import { Route } from '@angular/router';
import { provideEffects } from '@ngrx/effects';
@@ -319,17 +291,11 @@ export const routes: Route[] = [
];
-
-
-**Note:** Registering an effects class multiple times, either by `forRoot()`, `forFeature()`, or `provideEffects()`, (for example in different lazy loaded features) will not cause the effects to run multiple times. There is no functional difference between effects loaded by `root` and `feature`; the important difference between the functions is that `root` providers sets up the providers required for effects.
-
-
-
-## Alternative Way of Registering Effects
+### Alternative Way of Registering Effects
You can provide root-/feature-level effects with the provider `USER_PROVIDED_EFFECTS`.
-
+
providers: [
MoviesEffects,
{
@@ -340,37 +306,6 @@ providers: [
]
-
-
-The `EffectsModule.forFeature()` method or `provideEffects()` function must be added to the module imports/route config even if you only provide effects over token, and don't pass them through parameters. (Same goes for `EffectsModule.forRoot()`)
-
-
-
-## Standalone API in module-based apps
-
-If you have a module-based Angular application, you can still use standalone components. NgRx standalone APIs support this workflow as well.
-
-For module-based apps, you have the `EffectsModule.forRoot([...])` included in the `imports` array of your `AppModule`, which registers the root effects for dependency injection. For a standalone component with feature state/effects registered in its route configuration to successfully run effects, you will need to use the `provideEffects([...])` function in the `providers` array of your `AppModule` to register the injection token. For module-based with standalone components, you will simply have both.
-
-
-import { NgModule } from '@angular/core';
-import { EffectsModule, provideEffects } from '@ngrx/effects';
-
-import { MoviesEffects } from './effects/movies.effects';
-import * as actorsEffects from './effects/actors.effects';
-
-@NgModule({
- imports: [
- EffectsModule.forRoot(MoviesEffects, actorsEffects),
- ],
- providers: [
- provideEffects(MoviesEffects, actorsEffects)
- ]
-})
-export class AppModule {}
-
-
-
## Incorporating State
If additional metadata is needed to perform an effect besides the initiating action's `type`, we should rely on passed metadata from an action creator's `props` method.
@@ -388,7 +323,7 @@ export const login = createAction(
-import { Injectable } from '@angular/core';
+import { Injectable, inject } from '@angular/core';
import { Actions, ofType, createEffect } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, exhaustMap, map } from 'rxjs/operators';
@@ -401,8 +336,11 @@ import { AuthService } from '../services/auth.service';
@Injectable()
export class AuthEffects {
- login$ = createEffect(() =>
- this.actions$.pipe(
+ private actions$ = inject(Actions);
+ private authService = inject(AuthService);
+
+ login$ = createEffect(() => {
+ return this.actions$.pipe(
ofType(LoginPageActions.login),
exhaustMap(action =>
this.authService.login(action.credentials).pipe(
@@ -410,24 +348,19 @@ export class AuthEffects {
catchError(error => of(AuthApiActions.loginFailure({ error })))
)
)
- )
- );
-
- constructor(
- private actions$: Actions,
- private authService: AuthService
- ) {}
+ );
+ });
}
The `login` action has additional `credentials` metadata which is passed to a service to log the specific user into the application.
-However, there may be cases when the required metadata is only accessible from state. When state is needed, the RxJS `withLatestFrom` or the @ngrx/effects `concatLatestFrom` operators can be used to provide it.
+However, there may be cases when the required metadata is only accessible from state. When state is needed, the RxJS `withLatestFrom` or the @ngrx/effects `concatLatestFrom` operators can be used to provide it.
The example below shows the `addBookToCollectionSuccess$` effect displaying a different alert depending on the number of books in the collection state.
-import { Injectable } from '@angular/core';
+import { Injectable, inject } from '@angular/core';
import { Store } from '@ngrx/store';
import { Actions, ofType, createEffect, concatLatestFrom } from '@ngrx/effects';
import { tap } from 'rxjs/operators';
@@ -436,32 +369,30 @@ import * as fromBooks from '../reducers';
@Injectable()
export class CollectionEffects {
+ private actions$ = inject(Actions);
+ private store = inject(Store<fromBooks.State>);
+
addBookToCollectionSuccess$ = createEffect(
- () =>
- this.actions$.pipe(
+ () => {
+ return this.actions$.pipe(
ofType(CollectionApiActions.addBookSuccess),
- concatLatestFrom(action => this.store.select(fromBooks.getCollectionBookIds)),
- tap(([action, bookCollection]) => {
+ concatLatestFrom(_action => this.store.select(fromBooks.getCollectionBookIds)),
+ tap(([_action, bookCollection]) => {
if (bookCollection.length === 1) {
window.alert('Congrats on adding your first book!');
} else {
window.alert('You have added book number ' + bookCollection.length);
}
})
- ),
- { dispatch: false }
- );
-
- constructor(
- private actions$: Actions,
- private store: Store<fromBooks.State>
- ) {}
+ );
+ },
+ { dispatch: false });
}
-**Note:** For performance reasons, use a flattening operator like `concatLatestFrom` to prevent the selector from firing until the correct action is dispatched.
+For performance reasons, use a flattening operator like `concatLatestFrom` to prevent the selector from firing until the correct action is dispatched.
@@ -474,7 +405,7 @@ Because effects are merely consumers of observables, they can be used without ac
For example, imagine we want to track click events and send that data to our monitoring server. This can be done by creating an effect that listens to the `document` `click` event and emits the event data to our server.
-import { Injectable } from '@angular/core';
+import { Injectable, inject } from '@angular/core';
import { Observable, fromEvent } from 'rxjs';
import { concatMap } from 'rxjs/operators';
import { createEffect } from '@ngrx/effects';
@@ -483,14 +414,18 @@ import { UserActivityService } from '../services/user-activity.service';
@Injectable()
export class UserActivityEffects {
- trackUserActivity$ = createEffect(() =>
- fromEvent(document, 'click').pipe(
+ private userActivityService = inject(UserActivityService);
+
+ trackUserActivity$ = createEffect(() => {
+ return fromEvent(document, 'click').pipe(
concatMap(event => this.userActivityService.trackUserActivity(event)),
- ), { dispatch: false }
- );
-
- constructor(
- private userActivityService: UserActivityService,
- ) {}
+ );
+ }, { dispatch: false });
}
+
+
+
+An example of the `@ngrx/effects` in module-based applications is available at the [following link](https://v17.ngrx.io/guide/effects).
+
+
diff --git a/projects/ngrx.io/content/guide/effects/lifecycle.md b/projects/ngrx.io/content/guide/effects/lifecycle.md
index 957d205adf..7b7784cf11 100644
--- a/projects/ngrx.io/content/guide/effects/lifecycle.md
+++ b/projects/ngrx.io/content/guide/effects/lifecycle.md
@@ -6,12 +6,12 @@ After all the root effects have been added, the root effect dispatches a `ROOT_E
You can see this action as a lifecycle hook, which you can use in order to execute some code after all your root effects have been added.
-init$ = createEffect(() =>
- this.actions$.pipe(
+init$ = createEffect(() => {
+ return this.actions$.pipe(
ofType(ROOT_EFFECTS_INIT),
map(action => ...)
- )
-);
+ );
+});
## Effect Metadata
@@ -23,18 +23,19 @@ Sometimes you don't want effects to dispatch an action, for example when you onl
Usage:
-import { Injectable } from '@angular/core';
+import { Injectable, inject } from '@angular/core';
import { Actions, createEffect } from '@ngrx/effects';
import { tap } from 'rxjs/operators';
@Injectable()
export class LogEffects {
- constructor(private actions$: Actions) {}
+ private actions$ = inject(Actions);
- logActions$ = createEffect(() =>
- this.actions$.pipe(
- tap(action => console.log(action))
- ), { dispatch: false });
+ logActions$ = createEffect(() => {
+ return this.actions$.pipe(
+ tap(action => console.log(action))
+ );
+ }, { dispatch: false });
}
@@ -57,7 +58,7 @@ To disable resubscriptions add `{useEffectsErrorHandler: false}` to the `createE
metadata (second argument).
-import { Injectable } from '@angular/core';
+import { Injectable, inject } from '@angular/core';
import { Actions, ofType, createEffect } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, exhaustMap, map } from 'rxjs/operators';
@@ -69,9 +70,12 @@ import { AuthService } from '../services/auth.service';
@Injectable()
export class AuthEffects {
+ private actions$ = inject(Actions);
+ private authService = inject(AuthService);
+
logins$ = createEffect(
- () =>
- this.actions$.pipe(
+ () => {
+ return this.actions$.pipe(
ofType(LoginPageActions.login),
exhaustMap(action =>
this.authService.login(action.credentials).pipe(
@@ -80,14 +84,10 @@ export class AuthEffects {
)
)
// Errors are handled and it is safe to disable resubscription
- ),
+ );
+ },
{ useEffectsErrorHandler: false }
);
-
- constructor(
- private actions$: Actions,
- private authService: AuthService
- ) {}
}
@@ -109,10 +109,10 @@ import { EffectsModule, EFFECTS_ERROR_HANDLER } from '@ngrx/effects';
import { MoviesEffects } from './effects/movies.effects';
import { CustomErrorHandler, isRetryable } from '../custom-error-handler';
-export function effectResubscriptionHandler>T extends Action<(
- observable$: Observable>T<,
+export function effectResubscriptionHandler<T extends Action>(
+ observable$: Observable<T>,
errorHandler?: CustomErrorHandler
-): Observable>T< {
+): Observable<T> {
return observable$.pipe(
retryWhen(errors =>
errors.pipe(
@@ -129,19 +129,21 @@ export function effectResubscriptionHandler>T extends Action<(
);
}
-@NgModule({
- imports: [EffectsModule.forRoot([MoviesEffects])],
- providers: [
- {
- provide: EFFECTS_ERROR_HANDLER,
- useValue: effectResubscriptionHandler,
- },
- {
- provide: ErrorHandler,
- useClass: CustomErrorHandler
- }
- ],
-})
+bootstrapApplication(
+ AppComponent,
+ {
+ providers: [
+ {
+ provide: EFFECTS_ERROR_HANDLER,
+ useValue: effectResubscriptionHandler,
+ },
+ {
+ provide: ErrorHandler,
+ useClass: CustomErrorHandler
+ }
+ ],
+ }
+)
## Controlling Effects
@@ -181,16 +183,16 @@ import {
@Injectable()
export class UserEffects implements OnRunEffects {
- constructor(private actions$: Actions) {}
+ private actions$ = inject(Actions);
- updateUser$ = createEffect(() =>
- this.actions$.pipe(
+ updateUser$ = createEffect(() => {
+ return this.actions$.pipe(
ofType('UPDATE_USER'),
tap(action => {
console.log(action);
})
- ),
- { dispatch: false });
+ );
+ }, { dispatch: false });
ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>) {
return this.actions$.pipe(
diff --git a/projects/ngrx.io/content/guide/effects/operators.md b/projects/ngrx.io/content/guide/effects/operators.md
index a7a1dfb6b8..3e1e4d3144 100644
--- a/projects/ngrx.io/content/guide/effects/operators.md
+++ b/projects/ngrx.io/content/guide/effects/operators.md
@@ -17,7 +17,7 @@ The `ofType` operator takes up to 5 arguments with proper type inference. It can
take even more, however the type would be inferred as an `Action` interface.
-import { Injectable } from '@angular/core';
+import { Injectable, inject } from '@angular/core';
import { Actions, ofType, createEffect } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, exhaustMap, map } from 'rxjs/operators';
@@ -30,8 +30,11 @@ import { AuthService } from '../services/auth.service';
@Injectable()
export class AuthEffects {
- login$ = createEffect(() =>
- this.actions$.pipe(
+ private actions$ = inject(Actions);
+ private authService = inject(AuthService);
+
+ login$ = createEffect(() => {
+ return this.actions$.pipe(
// Filters by Action Creator 'login'
ofType(LoginPageActions.login),
exhaustMap(action =>
@@ -40,12 +43,7 @@ export class AuthEffects {
catchError(error => of(AuthApiActions.loginFailure({ error })))
)
)
- )
- );
-
- constructor(
- private actions$: Actions,
- private authService: AuthService
- ) {}
+ );
+ });
}