From f2e40f22ef5b186760763c6306c3c8dcf6987b05 Mon Sep 17 00:00:00 2001 From: Zak Henry Date: Sat, 25 Jan 2020 14:14:20 +0000 Subject: [PATCH] refactor(EffectsErrorHandler): Extract to separate file, rename default handler to describe purpose better. --- docs/effects/api.md | 56 ------------------- modules/effects/spec/effect_sources.spec.ts | 4 +- .../spec/effects_error_handler.spec.ts | 2 +- modules/effects/src/effect_sources.ts | 3 +- modules/effects/src/effects_error_handler.ts | 24 ++++++++ modules/effects/src/effects_module.ts | 4 +- modules/effects/src/effects_resolver.ts | 21 +------ modules/effects/src/index.ts | 3 +- modules/effects/src/tokens.ts | 2 +- .../content/guide/effects/lifecycle.md | 15 ++--- 10 files changed, 44 insertions(+), 90 deletions(-) create mode 100644 modules/effects/src/effects_error_handler.ts diff --git a/docs/effects/api.md b/docs/effects/api.md index ecae318702..99db895942 100644 --- a/docs/effects/api.md +++ b/docs/effects/api.md @@ -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( - observable$: Observable, - errorHandler?: CustomErrorHandler -): Observable { - 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 diff --git a/modules/effects/spec/effect_sources.spec.ts b/modules/effects/spec/effect_sources.spec.ts index df90f04347..a5354a0f2e 100644 --- a/modules/effects/spec/effect_sources.spec.ts +++ b/modules/effects/spec/effect_sources.spec.ts @@ -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', () => { @@ -37,7 +37,7 @@ describe('EffectSources', () => { providers: [ { provide: EFFECTS_ERROR_HANDLER, - useValue: resubscribeInCaseOfError, + useValue: defaultEffectsErrorHandler, }, EffectSources, EffectsRunner, diff --git a/modules/effects/spec/effects_error_handler.spec.ts b/modules/effects/spec/effects_error_handler.spec.ts index 71f17a4866..2d768a7cb1 100644 --- a/modules/effects/spec/effects_error_handler.spec.ts +++ b/modules/effects/spec/effects_error_handler.spec.ts @@ -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; diff --git a/modules/effects/src/effect_sources.ts b/modules/effects/src/effect_sources.ts index fc127471bd..53989ee7ef 100644 --- a/modules/effects/src/effect_sources.ts +++ b/modules/effects/src/effect_sources.ts @@ -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, diff --git a/modules/effects/src/effects_error_handler.ts b/modules/effects/src/effects_error_handler.ts new file mode 100644 index 0000000000..32d19bba3e --- /dev/null +++ b/modules/effects/src/effects_error_handler.ts @@ -0,0 +1,24 @@ +import { ErrorHandler } from '@angular/core'; +import { Action } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +export type EffectsErrorHandler = ( + observable$: Observable, + errorHandler: ErrorHandler +) => Observable; + +export const defaultEffectsErrorHandler: EffectsErrorHandler = < + T extends Action +>( + observable$: Observable, + errorHandler: ErrorHandler +): Observable => { + return observable$.pipe( + catchError(error => { + if (errorHandler) errorHandler.handleError(error); + // Return observable that produces this particular effect + return defaultEffectsErrorHandler(observable$, errorHandler); + }) + ); +}; diff --git a/modules/effects/src/effects_module.ts b/modules/effects/src/effects_module.ts index f8bd1f3406..26442a24c1 100644 --- a/modules/effects/src/effects_module.ts +++ b/modules/effects/src/effects_module.ts @@ -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 { @@ -50,7 +50,7 @@ export class EffectsModule { }, { provide: EFFECTS_ERROR_HANDLER, - useValue: resubscribeInCaseOfError, + useValue: defaultEffectsErrorHandler, }, EffectsRunner, EffectSources, diff --git a/modules/effects/src/effects_resolver.ts b/modules/effects/src/effects_resolver.ts index 16867193b4..05a8de1f47 100644 --- a/modules/effects/src/effects_resolver.ts +++ b/modules/effects/src/effects_resolver.ts @@ -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 = ( - observable$: Observable, - errorHandler: ErrorHandler -) => Observable; - export function mergeEffects( sourceInstance: any, globalErrorHandler: ErrorHandler, @@ -56,16 +52,3 @@ export function mergeEffects( return merge(...observables$); } - -export function resubscribeInCaseOfError( - observable$: Observable, - errorHandler: ErrorHandler -): Observable { - return observable$.pipe( - catchError(error => { - if (errorHandler) errorHandler.handleError(error); - // Return observable that produces this particular effect - return resubscribeInCaseOfError(observable$, errorHandler); - }) - ); -} diff --git a/modules/effects/src/index.ts b/modules/effects/src/index.ts index 9334dacda5..c84586adca 100644 --- a/modules/effects/src/index.ts +++ b/modules/effects/src/index.ts @@ -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'; diff --git a/modules/effects/src/tokens.ts b/modules/effects/src/tokens.ts index cc955571b9..666c666334 100644 --- a/modules/effects/src/tokens.ts +++ b/modules/effects/src/tokens.ts @@ -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( '@ngrx/effects Internal Root Guard' diff --git a/projects/ngrx.io/content/guide/effects/lifecycle.md b/projects/ngrx.io/content/guide/effects/lifecycle.md index 1e2bdb989b..4c33011950 100644 --- a/projects/ngrx.io/content/guide/effects/lifecycle.md +++ b/projects/ngrx.io/content/guide/effects/lifecycle.md @@ -92,20 +92,21 @@ export class AuthEffects { ### 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. ```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( observable$: Observable, @@ -135,7 +136,7 @@ export function effectResubscriptionHandler( useValue: effectResubscriptionHandler, }, { - provide: ErrorHandle, + provide: ErrorHandler, useClass: CustomErrorHandler } ],