or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions-filtering.mdadvanced-features.mdeffect-creation.mdindex.mdmodule-setup.mdtesting.md

advanced-features.mddocs/

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

```