Skip to content

Commit

Permalink
refactor(EffectsErrorHandler): Extract to separate file, rename defau…
Browse files Browse the repository at this point in the history
…lt handler to describe purpose better.
  • Loading branch information
zak-cloudnc committed Jan 25, 2020
1 parent 693161b commit 25d8ad7
Show file tree
Hide file tree
Showing 10 changed files with 45 additions and 90 deletions.
56 changes: 0 additions & 56 deletions docs/effects/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,62 +204,6 @@ export class UserEffects implements OnRunEffects {
}
```

### EffectsErrorHandler

By default, if an effect has `{useEffectsErrorHandler: true}` (the effect metadata default), when the effect encounters an error it
is automatically resubscribed to, and the Angular `ErrorHandler.handleError` method is called with the error, and the
effect observable resubscribed to.

If you want to customize this behavior, for example if you have a [custom error handler](https://angular.io/api/core/ErrorHandler) that needs specific input, or you
only want to resubscribe on certain errors etc, you may provide a custom handler using the `EFFECTS_ERROR_HANDLER`
injection token.

Usage:

```ts
import { EffectsModule, EFFECTS_ERROR_HANDLER } from '@ngrx/effects';
import { MovieEffects } from './effects/movie.effects';
import { CustomErrorHandler, isRetryable } from '../custom-error-handler';
import { Action } from '@ngrx/store';
import { Observable, throwError } from 'rxjs';
import { retryWhen, mergeMap } from 'rxjs/operators';

export function effectResubscriptionHandler<T extends Action>(
observable$: Observable<T>,
errorHandler?: CustomErrorHandler
): Observable<T> {
return observable$.pipe(
retryWhen(errors =>
errors.pipe(
mergeMap(e => {
if (isRetryable(e)) {
return errorHandler.handleRetryableError(e);
}

errorHandler.handleError(e);
return throwError(e);
})
)
)
);
}

@NgModule({
imports: [EffectsModule.forRoot([MovieEffects])],
providers: [
{
provide: EFFECTS_ERROR_HANDLER,
useValue: effectResubscriptionHandler,
},
{
provide: ErrorHandle,
useClass: CustomErrorHandler,
},
],
})
export class AppModule {}
```

## Utilities

### mergeEffects
Expand Down
4 changes: 2 additions & 2 deletions modules/effects/spec/effect_sources.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ import {
EffectsErrorHandler,
Actions,
} from '../';
import { defaultEffectsErrorHandler } from '../src/effects_error_handler';
import { EffectsRunner } from '../src/effects_runner';
import { Store } from '@ngrx/store';
import { resubscribeInCaseOfError } from '../src/effects_resolver';
import { ofType } from '../src';

describe('EffectSources', () => {
Expand All @@ -37,7 +37,7 @@ describe('EffectSources', () => {
providers: [
{
provide: EFFECTS_ERROR_HANDLER,
useValue: resubscribeInCaseOfError,
useValue: defaultEffectsErrorHandler,
},
EffectSources,
EffectsRunner,
Expand Down
2 changes: 1 addition & 1 deletion modules/effects/spec/effects_error_handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { createEffect, EFFECTS_ERROR_HANDLER, EffectsModule } from '..';

describe('NgRx Effects Error Handler spec', () => {
describe('Effects Error Handler', () => {
let subscriptionCount: number;
let globalErrorHandler: jasmine.Spy;
let storeNext: jasmine.Spy;
Expand Down
3 changes: 2 additions & 1 deletion modules/effects/src/effect_sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
reportInvalidActions,
EffectNotification,
} from './effect_notification';
import { mergeEffects, EffectsErrorHandler } from './effects_resolver';
import { EffectsErrorHandler } from './effects_error_handler';
import { mergeEffects } from './effects_resolver';
import {
onIdentifyEffectsKey,
onRunEffectsKey,
Expand Down
25 changes: 25 additions & 0 deletions modules/effects/src/effects_error_handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ErrorHandler } from '@angular/core';
import { Action } from '@ngrx/store';
import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';

export type EffectsErrorHandler = <T extends Action>(
observable$: Observable<T>,
errorHandler: ErrorHandler
) => Observable<T>;

// this is the default error handler provided in EffectsModule.forRoot
export const defaultEffectsErrorHandler: EffectsErrorHandler = <
T extends Action
>(
observable$: Observable<T>,
errorHandler: ErrorHandler
): Observable<T> => {
return observable$.pipe(
catchError(error => {
if (errorHandler) errorHandler.handleError(error);
// Return observable that produces this particular effect
return defaultEffectsErrorHandler(observable$, errorHandler);
})
);
};
4 changes: 2 additions & 2 deletions modules/effects/src/effects_module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { Actions } from './actions';
import { EffectSources } from './effect_sources';
import { EffectsFeatureModule } from './effects_feature_module';
import { resubscribeInCaseOfError } from './effects_resolver';
import { defaultEffectsErrorHandler } from './effects_error_handler';
import { EffectsRootModule } from './effects_root_module';
import { EffectsRunner } from './effects_runner';
import {
Expand Down Expand Up @@ -50,7 +50,7 @@ export class EffectsModule {
},
{
provide: EFFECTS_ERROR_HANDLER,
useValue: resubscribeInCaseOfError,
useValue: defaultEffectsErrorHandler,
},
EffectsRunner,
EffectSources,
Expand Down
21 changes: 2 additions & 19 deletions modules/effects/src/effects_resolver.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { Action } from '@ngrx/store';
import { merge, Notification, Observable } from 'rxjs';
import { ignoreElements, map, materialize, catchError } from 'rxjs/operators';
import { ignoreElements, map, materialize } from 'rxjs/operators';

import { EffectNotification } from './effect_notification';
import { getSourceMetadata } from './effects_metadata';
import { EffectsErrorHandler } from './effects_error_handler';
import { getSourceForInstance } from './utils';
import { ErrorHandler } from '@angular/core';

export type EffectsErrorHandler = <T extends Action>(
observable$: Observable<T>,
errorHandler: ErrorHandler
) => Observable<T>;

export function mergeEffects(
sourceInstance: any,
globalErrorHandler: ErrorHandler,
Expand Down Expand Up @@ -56,16 +52,3 @@ export function mergeEffects(

return merge(...observables$);
}

export function resubscribeInCaseOfError<T extends Action>(
observable$: Observable<T>,
errorHandler: ErrorHandler
): Observable<T> {
return observable$.pipe(
catchError(error => {
if (errorHandler) errorHandler.handleError(error);
// Return observable that produces this particular effect
return resubscribeInCaseOfError(observable$, errorHandler);
})
);
}
3 changes: 2 additions & 1 deletion modules/effects/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ export { createEffect } from './effect_creator';
export { EffectConfig } from './models';
export { Effect } from './effect_decorator';
export { getEffectsMetadata } from './effects_metadata';
export { mergeEffects, EffectsErrorHandler } from './effects_resolver';
export { mergeEffects } from './effects_resolver';
export { EffectsErrorHandler } from './effects_error_handler';
export { EffectsMetadata, CreateEffectMetadata } from './models';
export { Actions, ofType } from './actions';
export { EffectsModule } from './effects_module';
Expand Down
2 changes: 1 addition & 1 deletion modules/effects/src/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { InjectionToken, Type } from '@angular/core';
import { EffectsErrorHandler } from './effects_resolver';
import { EffectsErrorHandler } from './effects_error_handler';

export const _ROOT_EFFECTS_GUARD = new InjectionToken<void>(
'@ngrx/effects Internal Root Guard'
Expand Down
15 changes: 8 additions & 7 deletions projects/ngrx.io/content/guide/effects/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,21 @@ export class AuthEffects {
</code-example>

### Customizing the Effects Error Handler
Starting with version 9 the behavior of the default resubscription handler can be customized

The behavior of the default resubscription handler can be customized
by providing a custom handler using the `EFFECTS_ERROR_HANDLER` injection token.

This will allow you to provide your own custom behavior such as only retrying on
certain "retryable" errors, or retrying a set number of times etc.
This allows you to provide a custom behavior, such as only retrying on
certain "retryable" errors, or with maximum number of retries.

<code-example header="customise-error-handler.effects.ts">
```ts
import { Observable, throwError } from 'rxjs';
import { retryWhen, mergeMap } from 'rxjs/operators';
import { Action } from '@ngrx/store';
import { EffectsModule, EFFECTS_ERROR_HANDLER } from '@ngrx/effects';
import { MovieEffects } from './effects/movie.effects';
import { CustomErrorHandler, isRetryable } from '../custom-error-handler';
import { Action } from '@ngrx/store';
import { Observable, throwError } from 'rxjs';
import { retryWhen, mergeMap } from 'rxjs/operators';

export function effectResubscriptionHandler<T extends Action>(
observable$: Observable<T>,
Expand Down Expand Up @@ -135,7 +136,7 @@ export function effectResubscriptionHandler<T extends Action>(
useValue: effectResubscriptionHandler,
},
{
provide: ErrorHandle,
provide: ErrorHandler,
useClass: CustomErrorHandler
}
],
Expand Down

0 comments on commit 25d8ad7

Please sign in to comment.