'How to refer to component object within intercept/arrow function?

Here's my code:

export class NetworkInterceptor implements HttpInterceptor {
  constructor(private router: Router, private ngZone: NgZone) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    // ...

    return next.handle(request).pipe(
      finalize(() => {
        // ...
      }),
      tap({
        next(res) {},
        error(error) {
          if (error instanceof HttpErrorResponse) {
            if (error.status === 401) {
              this.ngZone.run(() => this.router.navigateByUrl('/account/signin'));
            }
          }
        },
        complete() {},
      })
    );
  }
}

But can't use

this.ngZone.run(() => this.router.navigateByUrl('/account/signin'))

within intercept, since this doesn't refers to component's object.

What's the correct way to do this?



Solution 1:[1]

If the intention is to navigate to a route using the router service, on a 401 error, you don't need to use ngZone. ngZone is a service we use when bringing into angular events from the outside of angular's scope/zone.

What you need is to use catchError in a more simplistic way, you will be set for the win with something like the following:

export class NetworkInterceptor implements HttpInterceptor {
  constructor(private router: Router) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    // ...
    return next.handle(req).pipe(
      catchError((err) => {
        if (err instanceof HttpErrorResponse && err.status === 401) {
          this.router.navigateByUrl('/account/signin');
        }
        return throwError(err);
      })
    );
  }
}

Solution 2:[2]

Just use an arrow functino for your tap payload


tap({
  next: () => {...},
  error: () => {...},
  complete: () => {...},
})

EDIT personal opinion, but you should rather use catchError operator, which seems more suited in your case.

Solution 3:[3]

Try declaring a local variable for the router instance inside the intercept function. And then you use that variable in your code.

intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    // ...
    const interceptorRouter = this.router;
    return next.handle(request).pipe(
        finalize(() => {
            // ...
        }),
        tap({
            next(res) { },
            error(error) {
                if (error instanceof HttpErrorResponse) {
                    if (error.status === 401) {
                        this.ngZone.run(() => interceptorRouter.navigateByUrl('/account/signin'));
                    }
                }
            },
            complete() { }
        })
    );
}

Solution 4:[4]

This is a very simple approach that I had in a past application, I don't know if you find that elegant enough:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private router: Router, @Inject(WINDOW) private window: Window) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error?.status === 401) {
          logout(this.window, this.router);
        }
        return throwError(error);
      })
    );
  }
}

You can inject NgZone if the same with Window & Router.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 The Fabio
Solution 2 temp_user
Solution 3 JSantaCL
Solution 4 StPaulis