I am using a global error handler and a Graphql provider that handles all requests and errors. However, when throwing an error, it is not caught by the global handler as it is outside it's angular. Throwing an error inside a component works though.
I'm not using ZoneJs.
Here is my Graphql provider code:
import { Apollo, APOLLO_OPTIONS } from 'apollo-angular';
import { HttpLink } from 'apollo-angular/http';
import { ApplicationConfig, inject } from '@angular/core';
import { ApolloClientOptions, InMemoryCache, split } from '@apollo/client/core';
import { environment } from '../environments/environment';
import { onError } from '@apollo/client/link/error';
import { Router } from '@angular/router';
import { HttpHeaders } from '@angular/common/http';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { getMainDefinition } from '@apollo/client/utilities';
import { createClient } from 'graphql-ws';
// @ts-ignore
import extractFiles from 'extract-files/extractFiles.mjs';
// @ts-ignore
import isExtractableFile from 'extract-files/isExtractableFile.mjs';
const uri = `${environment.base}/graphql`
export function apolloOptionsFactory(): ApolloClientOptions<any> {
const httpLink = inject(HttpLink);
const router = inject(Router)
const wsLink = new GraphQLWsLink(
createClient({
url: (environment.base).toString()
})
)
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(e => {
if (e.message === 'Unauthorized')
router.navigate(['auth/login'])
throw new Error(e.message)
})
}
if (networkError) {
throw new Error(networkError.message)
}
})
return {
link: split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
errorLink.concat(httpLink.create({
uri,
extractFiles: (body) => extractFiles(body, isExtractableFile),
headers: new HttpHeaders({ 'x-apollo-operation-name': 'some-header-with-file-upload'})
}))
),
cache: new InMemoryCache()
}
}
export const graphqlProvider: ApplicationConfig['providers'] = [
Apollo,
{
provide: APOLLO_OPTIONS,
useFactory: apolloOptionsFactory,
},
];
Error handling service
import { ErrorHandler, Injectable, Injector, inject } from "@angular/core";
import { HttpErrorResponse } from "@angular/common/http";
import { ErrorService } from "./error.service";
import { NotificationService } from "./notification.service";
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
injector: Injector = inject(Injector)
handleError(error: Error | HttpErrorResponse): void {
console.log('SOMETHING SHOULD HAPPEN SINCE ALL ERRORS ARE HANDLED HERE')
const errorService = this.injector.get(ErrorService)
const notifier = this.injector.get(NotificationService)
let message: string
message = errorService.getClientErrorMessage(error)
notifier.showError(message)
}
}
Graphql methods service
export class GraphqlService {
public postsQuery!: QueryRef<any>
private readonly apollo = inject(Apollo)
public mutateFn(mutation: TypedDocumentNode<unknown, any>, variables?: any, isFile: boolean = false): Observable<MutationResult<unknown> | any> {
return this.apollo.mutate({
mutation: mutation,
variables: variables,
errorPolicy: 'all',
context: {
useMultipart: isFile
}
}).pipe(map((v) => {
return v.data
})
)
}
I've tried using ApplicationRef.tick()
but I'm getting an error ApplicationRef.tick() is running recursively
Is there a better way to do this?
EDIT**
Updated code following Naren's suggestion
// @ts-ignore
const errorCallback = ({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach((e: any) => {
if (e.message === 'Unauthorized')
router.navigate(['auth/login'])
throw new Error(e.message)
})
}
if (networkError) {
throw new Error(networkError.message)
}
}
// @ts-ignore
const errorLink = onError(errorCallback.bind(this))
Angular's error handler isn't able to catch any error that happens outside of angular when running a zoneless app.
What you could do though, is capturing global errors is your own handler with
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor() {
window.addEventListener('unhandledrejection', event => {
this.handleError(event.error);
event.preventDefault()
});
}
handleError(error : any) : void {
...
}
}