0
# Advanced Features
1
2
Lifecycle hooks, error handling, and advanced effect management capabilities for sophisticated NgRx Effects usage patterns and custom effect behaviors.
3
4
## Capabilities
5
6
### Lifecycle Hooks
7
8
Interfaces for controlling effect registration, execution, and initialization with fine-grained control over effect behavior.
9
10
```typescript { .api }
11
/**
12
* Hook for providing unique identifiers for effect instances
13
* Useful for debugging and effect instance tracking
14
*/
15
interface OnIdentifyEffects {
16
/**
17
* Provide unique identifier for this effect instance
18
* @returns Unique string identifier
19
*/
20
ngrxOnIdentifyEffects(): string;
21
}
22
23
/**
24
* Hook for controlling effect lifecycle and execution
25
* Allows custom handling of resolved effects before they run
26
*/
27
interface OnRunEffects {
28
/**
29
* Control effect execution with custom logic
30
* @param resolvedEffects$ - Stream of resolved effect notifications
31
* @returns Modified stream of effect notifications
32
*/
33
ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>): Observable<EffectNotification>;
34
}
35
36
/**
37
* Hook for dispatching custom actions after effect registration
38
* Useful for initialization actions or setup logic
39
*/
40
interface OnInitEffects {
41
/**
42
* Return action to dispatch after effect registration
43
* @returns Action to dispatch during effect initialization
44
*/
45
ngrxOnInitEffects(): Action;
46
}
47
```
48
49
**Usage Examples:**
50
51
```typescript
52
import { Injectable } from "@angular/core";
53
import { Actions, createEffect, ofType, OnIdentifyEffects, OnRunEffects, OnInitEffects } from "@ngrx/effects";
54
55
@Injectable()
56
export class AdvancedEffects implements OnIdentifyEffects, OnRunEffects, OnInitEffects {
57
constructor(private actions$: Actions) {}
58
59
// Provide unique identifier for debugging
60
ngrxOnIdentifyEffects(): string {
61
return 'AdvancedEffects-' + Math.random().toString(36).substr(2, 9);
62
}
63
64
// Control effect execution with custom logic
65
ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>): Observable<EffectNotification> {
66
return resolvedEffects$.pipe(
67
// Add custom logging
68
tap(notification => {
69
console.log(`Effect ${notification.propertyName} from ${notification.sourceName} executed`);
70
}),
71
// Add delay for testing or rate limiting
72
delay(100),
73
// Filter effects based on conditions
74
filter(notification => {
75
// Only run certain effects in production
76
if (environment.production && notification.propertyName === 'debugEffect$') {
77
return false;
78
}
79
return true;
80
})
81
);
82
}
83
84
// Dispatch initialization action
85
ngrxOnInitEffects(): Action {
86
return AppActions.effectsInitialized({
87
effectsClass: 'AdvancedEffects',
88
timestamp: Date.now()
89
});
90
}
91
92
// Regular effects
93
loadData$ = createEffect(() =>
94
this.actions$.pipe(
95
ofType(AppActions.loadData),
96
switchMap(() =>
97
this.dataService.loadData().pipe(
98
map(data => AppActions.loadDataSuccess({ data }))
99
)
100
)
101
)
102
);
103
104
debugEffect$ = createEffect(() =>
105
this.actions$.pipe(
106
tap(action => console.log('Debug action:', action))
107
),
108
{ dispatch: false }
109
);
110
}
111
112
// Example with initialization effect
113
@Injectable()
114
export class AuthEffects implements OnInitEffects {
115
constructor(private actions$: Actions, private authService: AuthService) {}
116
117
ngrxOnInitEffects(): Action {
118
// Check authentication status on effect initialization
119
const isAuthenticated = this.authService.isAuthenticated();
120
return isAuthenticated
121
? AuthActions.checkAuthSuccess()
122
: AuthActions.checkAuthFailure();
123
}
124
125
// Auth-related effects...
126
}
127
```
128
129
### Error Handling
130
131
Comprehensive error handling system with customizable error handlers and retry mechanisms.
132
133
```typescript { .api }
134
/**
135
* Type definition for custom effects error handlers
136
* @param observable$ - Observable that may emit errors
137
* @param errorHandler - Angular ErrorHandler service
138
* @returns Observable with error handling applied
139
*/
140
type EffectsErrorHandler = <T extends Action>(
141
observable$: Observable<T>,
142
errorHandler: ErrorHandler
143
) => Observable<T>;
144
145
/**
146
* Default error handling implementation with retry logic
147
* @param observable$ - Observable to apply error handling to
148
* @param errorHandler - Angular ErrorHandler service
149
* @param retryAttemptLeft - Number of retry attempts remaining (default: 10)
150
* @returns Observable with default error handling and retry logic
151
*/
152
function defaultEffectsErrorHandler<T extends Action>(
153
observable$: Observable<T>,
154
errorHandler: ErrorHandler,
155
retryAttemptLeft: number = 10
156
): Observable<T>;
157
158
/** Injection token for custom error handlers */
159
const EFFECTS_ERROR_HANDLER: InjectionToken<EffectsErrorHandler>;
160
```
161
162
**Usage Examples:**
163
164
```typescript
165
import { Injectable, ErrorHandler } from "@angular/core";
166
import { EFFECTS_ERROR_HANDLER, defaultEffectsErrorHandler } from "@ngrx/effects";
167
168
// Custom error handler implementation
169
export const customEffectsErrorHandler: EffectsErrorHandler = <T extends Action>(
170
observable$: Observable<T>,
171
errorHandler: ErrorHandler
172
) => {
173
return observable$.pipe(
174
catchError((error, caught) => {
175
// Custom logging
176
console.error('Effect error:', error);
177
178
// Send to error reporting service
179
errorReportingService.reportError(error);
180
181
// Custom retry logic based on error type
182
if (error.status === 503) {
183
// Retry server unavailable errors with exponential backoff
184
return caught.pipe(delay(2000));
185
} else if (error.status >= 400 && error.status < 500) {
186
// Don't retry client errors
187
errorHandler.handleError(error);
188
return EMPTY;
189
} else {
190
// Use default handling for other errors
191
return defaultEffectsErrorHandler(caught, errorHandler, 3);
192
}
193
})
194
);
195
};
196
197
// Register custom error handler
198
bootstrapApplication(AppComponent, {
199
providers: [
200
provideStore({}),
201
provideEffects(AppEffects),
202
{
203
provide: EFFECTS_ERROR_HANDLER,
204
useValue: customEffectsErrorHandler
205
}
206
]
207
});
208
209
// Effect with custom error handling disabled
210
@Injectable()
211
export class CustomErrorEffects {
212
customHandlingEffect$ = createEffect(() =>
213
this.actions$.pipe(
214
ofType(SomeActions.riskyAction),
215
switchMap(action =>
216
this.riskyService.performOperation(action.data).pipe(
217
map(result => SomeActions.success({ result })),
218
catchError(error => {
219
// Custom error handling logic
220
if (error.code === 'BUSINESS_ERROR') {
221
return of(SomeActions.businessError({ error: error.message }));
222
} else {
223
// Log and continue
224
console.error('Unexpected error:', error);
225
return EMPTY;
226
}
227
})
228
)
229
)
230
),
231
{ useEffectsErrorHandler: false } // Disable automatic error handling
232
);
233
}
234
235
// Error handler with different strategies per effect
236
export class ConditionalErrorHandler implements EffectsErrorHandler {
237
constructor(private notificationService: NotificationService) {}
238
239
<T extends Action>(
240
observable$: Observable<T>,
241
errorHandler: ErrorHandler
242
): Observable<T> {
243
return observable$.pipe(
244
catchError((error, caught) => {
245
// Different handling based on error context
246
if (error.effectName?.includes('critical')) {
247
// Critical effects - show user notification and don't retry
248
this.notificationService.showError('Critical operation failed');
249
errorHandler.handleError(error);
250
return EMPTY;
251
} else if (error.effectName?.includes('background')) {
252
// Background effects - silent retry
253
return caught.pipe(delay(5000));
254
} else {
255
// Default handling
256
return defaultEffectsErrorHandler(caught, errorHandler);
257
}
258
})
259
);
260
}
261
}
262
```
263
264
### Effect Notification System
265
266
Internal notification system for effect execution and monitoring.
267
268
```typescript { .api }
269
/**
270
* Notification wrapper for effect outputs and metadata
271
*/
272
class EffectNotification {
273
/** The effect observable */
274
effect: Observable<any>;
275
/** Property name of the effect in the source class */
276
propertyName: PropertyKey;
277
/** Name of the source class containing the effect */
278
sourceName: string;
279
/** Instance of the source class */
280
sourceInstance: any;
281
/** RxJS notification containing the effect result */
282
notification: Notification<Action | null>;
283
}
284
```
285
286
### Effects Initialization
287
288
Constants and functions for effects system initialization.
289
290
```typescript { .api }
291
/** Action type for effects initialization */
292
const ROOT_EFFECTS_INIT: "@ngrx/effects/init";
293
294
/**
295
* Action creator for effects initialization
296
* @returns Action indicating effects system initialization
297
*/
298
function rootEffectsInit(): Action;
299
```
300
301
**Usage Examples:**
302
303
```typescript
304
// Listen for effects initialization
305
@Injectable()
306
export class AppEffects {
307
effectsInit$ = createEffect(() =>
308
this.actions$.pipe(
309
ofType(ROOT_EFFECTS_INIT),
310
map(() => AppActions.systemInitialized())
311
)
312
);
313
}
314
315
// Custom initialization handling
316
@Injectable()
317
export class InitializationEffects {
318
handleInit$ = createEffect(() =>
319
this.actions$.pipe(
320
ofType(ROOT_EFFECTS_INIT),
321
withLatestFrom(this.store.select(selectAppConfig)),
322
switchMap(([action, config]) => [
323
AppActions.configLoaded({ config }),
324
AppActions.startBackgroundTasks(),
325
...(config.enableAnalytics ? [AnalyticsActions.initialize()] : [])
326
])
327
)
328
);
329
}
330
```
331
332
### Advanced Effect Patterns
333
334
**Effect with State Machine:**
335
336
```typescript
337
@Injectable()
338
export class StateMachineEffects implements OnRunEffects {
339
private state = 'idle';
340
341
ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>): Observable<EffectNotification> {
342
return resolvedEffects$.pipe(
343
tap(() => {
344
// Update internal state machine
345
this.updateState();
346
}),
347
filter(() => this.canExecuteEffects())
348
);
349
}
350
351
private updateState() {
352
// State machine logic
353
switch (this.state) {
354
case 'idle':
355
this.state = 'processing';
356
break;
357
case 'processing':
358
this.state = 'completed';
359
break;
360
}
361
}
362
363
private canExecuteEffects(): boolean {
364
return this.state !== 'blocked';
365
}
366
}
367
```
368
369
**Rate-Limited Effects:**
370
371
```typescript
372
@Injectable()
373
export class RateLimitedEffects implements OnRunEffects {
374
private lastExecution = 0;
375
private readonly rateLimitMs = 1000;
376
377
ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>): Observable<EffectNotification> {
378
return resolvedEffects$.pipe(
379
filter(() => {
380
const now = Date.now();
381
if (now - this.lastExecution > this.rateLimitMs) {
382
this.lastExecution = now;
383
return true;
384
}
385
return false;
386
})
387
);
388
}
389
}
390
```