Reputation: 1137
Is there any way to use throw
with an Error object inside ngrx-effects streams without completing the stream?
I've read these great answers on why the stream is being killed by throwing an error:
@ngrx Effect does not run the second time
https://github.com/ngrx/platform/issues/646
My question is if I'm implementing the Angular ErrorHandler
to catch errors, if I'm going to be able to use that with ngrx effects.
@Effect()
loginUserEffect: Observable<loginActions.Actions> = this.actions$
.ofType(loginActions.LOGIN_USER)
.map((action: loginActions.LoginUser) => action.payload)
.mergeMap(payload => {
return this.loginService
.authenticate(payload)
.map(response => new loginActions.LoginUserSuccess(response))
.catch((error: HttpErrorResponse) =>
of(new loginActions.LoginUserFailure(error))
)
})
@Effect({ dispatch: false })
loginUserEffectSuccess$ = this.actions$
.ofType(loginActions.LOGIN_USER_SUCCESS)
.do(() => this.router.navigate(['/account-home']))
@Effect({ dispatch: false })
loginUserEffectFailure$ = this.actions$
.ofType(loginActions.LOGIN_USER_FAILURE)
.map((action: loginActions.LoginUserFailure) => {
throw action.payload // Stream completes
})
I imagine I could create some way of dealing with errors that doesn't involve throwing anything, but wanted to make sure I needed to go that route or if there was a way to keep both of them peacefully coexisting.
Currently in my class that implements ErrorHander
, I have this:
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
private messagesService: MessagesService
private router: Router
constructor(
private injector: Injector, // DI workaround (https://stackoverflow.com/a/41585902)
private errorLoggerService: ErrorLoggerService
) {
// DI workaround (https://stackoverflow.com/a/41585902)
setTimeout(() => (this.messagesService = injector.get(MessagesService)))
setTimeout(() => (this.router = injector.get(Router)))
}
handleError(error) {
if (error instanceof HttpErrorResponse) {
this.handleServerError(error)
} else if (error instanceof ClientError) {
this.handleClientError(error)
} else {
this.handleUnexpectedError(error)
}
}
Which means I just throw errors and they are handled based on the type
Upvotes: 2
Views: 4541
Reputation: 1137
I ended up creating an Error action:
@Effect()
loginUserEffectFailure$: Observable<
errorsActions.Actions
> = this.actions$
.ofType(loginActions.LOGIN_USER_FAILURE)
.map(
(action: loginActions.LoginUserFailure) =>
new errorsActions.Error(action.payload)
)
and having that call the error handler in a dedicated effect:
@Effect({ dispatch: false })
errorEffect$ = this.actions$
.ofType(errorsActions.ERROR)
.map((action: errorsActions.Error) =>
this.clientErrorHandler.handleError(action.payload)
)
I left the global error handler just for unexpected exceptions to be caught and logged.
Upvotes: 5
Reputation: 58400
If you want to throw an error that will not be caught by the observable and will instead be reported to a global error handler, you have throw the error from outside of the observable's call stack.
You could do something like this:
@Effect({ dispatch: false })
loginUserEffectFailure$ = this.actions$
.ofType(loginActions.LOGIN_USER_FAILURE)
.do(action => setTimeout(() => { throw action.payload; }, 0))
})
However, as your effects are in a class that already uses Angular's DI mechanism, I would suggest you instead inject your GlobalErrorHandler
and call it directly.
Without the setTimeout
calls I think it would be clearer and easier to test, too.
Upvotes: 2