1

I'm setting up a JWT interceptor in Angular 18.. I had this set up in Angular 8, although that was a completely different setup using a class which inherits HttpInterceptor. here I am using function injection...

In app.module.ts I have the providers set as such:

  providers: [
    provideHttpClient(
      withInterceptors([jwtInterceptor])
    )
  ],

The jwt.interceptor.ts is as such:

import { Inject, Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpHandlerFn } from '@angular/common/http';
import { Observable } from 'rxjs';

import { AuthenticationService } from '../_services';

export function jwtInterceptor(request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
  // add auth header with jwt if user is logged in and request is to the api url
  const _authenticationService: AuthenticationService = Inject(AuthenticationService) as AuthenticationService;
  const _baseUrl: string = Inject('BASE_URL') as string;

  const currentUser = JSON.parse(JSON.stringify(_authenticationService.UserValue));
  const isLoggedIn = _authenticationService.IsLoggedIn;
  const isApiUrl = request.url.indexOf(_baseUrl) == 0;
  if (isLoggedIn && isApiUrl) {
    request = request.clone({
      setHeaders: {
        Authorization: `Bearer ${currentUser.authData}`
      }
    });
  }

  return next(request);
}

now for the things I'm trying to inject:

AuthenticationService (important to note here that injecting base url in the constructor works, also I can inject the AuthenticationService itself into other constructors, just not in this httpInterceptors function):

import { Inject, Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';


import { LoginInformation, User } from '../_dataObjects';

@Injectable({ providedIn: 'root' })
export class AuthenticationService {
  private _userSubject: BehaviorSubject<User>;
  public User: Observable<User>;

  constructor(private _router: Router, private http: HttpClient, @Inject('BASE_URL') private _baseUrl: string) {
    var username = localStorage.getItem('user') ?? '';

    this._userSubject = new BehaviorSubject<User>(username.length > 0 ? JSON.parse(username) : new User());
    this.User = this._userSubject.asObservable();    
  }

  ...
}

Base url is defined in main.ts providers as such:

const providers = [
  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }
];
3
  • So what exactly is the question? You have mentioned what you did, but what are you looking for? Commented Jul 10, 2024 at 21:02
  • @funkycoder the question was in the function jwtInterceptor I needed to be able to inject the AuthenticationService and base_url as I would in the constructor of a legacy HttpInterceptor... Failing to find a way to do this I found a way to use an HttpInterceptor class instead utilizing provideHttpClient(withInterceptorsFromDi() ) as illustrated in my answer below. If you know a way to perform what I was trying to do injecting in the function, I am still ears to it Commented Jul 10, 2024 at 21:10
  • Please accept my answer if that helped. Thanks. Commented Jul 13, 2024 at 11:11

5 Answers 5

5

I decided to go back t a class based interceptor... In app.module.ts I've changed the providers as such:

  providers: [
    provideHttpClient(
      withInterceptorsFromDi()
    ),
    { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }
  ],

then my interceptor has changed to be as such:

@Injectable()
export class JwtInterceptor implements HttpInterceptor {
  constructor(private _authenticationService: AuthenticationService, @Inject('BASE_URL') private _baseUrl: string) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // add auth header with jwt if user is logged in and request is to the api url
    const currentUser = this._authenticationService.UserValue;
    const isLoggedIn = this._authenticationService.IsLoggedIn;
    const isApiUrl = request.url.indexOf(this._baseUrl) == 0;
    if (isLoggedIn && isApiUrl) {
      request = request.clone({
        setHeaders: {
          Authorization: `Bearer ${currentUser.authData}`
        }
      });
    }

    return next.handle(request);
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

yes, this worked for me as well.
1

@Jessie Lulham, I think you are simply using the incorrect Inject from @angular/core.

use this instead (lowercase inject, instead of the uppercase Inject):

import { inject } from '@angular/core';

Then you should be able to inject any injectable resource as:

const authenticationService = inject(AuthenticationService);

// do whatever
if (authenticationService.isLoggedIn) {
  // blah blah
}

3 Comments

this solution has led me to this error: inject() must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with runInInjectionContext An error I recently had to overcome while integrating a variety of datatable plugins (Ag Grid, mattables, primeng, etc) I corrected those inject issues by setting the path to those dependencies in tsconfig... I don't see that as a solution here. It would appear my best option would be to continue using InterceptorsFromDi, although eliminating the overhead of class creation would be nice
@JessieLulham try using an arrow function instead of a named function
@JessieLulham, just as the error indicates, inject() needs to be called from a constructor. If you call it from any other function, it will throw that error.
0

The way to provide the interceptor depends on what type it is. Either functional and class/DI-based.

provideHttpClient(withInterceptors([jwtInterceptor])

is only used to provide the function-based http interceptors: i.e. that implements the HttpInterceptorFn interface.

withInterceptorsFromDi()

is the way to provide class-cased http interecptors: i.e. that implements the HttpInterceptor interface.

Migrating to the functional type

In terms of migrating to the functional type, the key change is to use Angular's inject API to retrieve dependencies.

In your example:

const authenticationService = inject(AuthenticationService);

Comments

0

I would add the following:

If you move to the HttpInterceptorFn pattern your function should look something like this:

export const customInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {

    const exampleService = inject(MyService);

    // Your code e.g. XSRF, checking url etc.
    // exampleService.foo();
    return next(req);
};

Notice that we're injecting a required service in the function itself, whereas if we were using the class based approach we'd do the following:

constructor(private exampleService: ExampleService) {}

And to use the function approach we modify our ApplicationConfig like so:

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }), 
    provideRouter(routes),
    provideHttpClient(
      withInterceptors([
        customInterceptorFn
      ])
    )
  ]
};

Comments

0

If you are using Angular 18, go inside main.ts and make following changes:

bootstrapApplication(AppComponent, {providers: [
  provideHttpClient(
    withInterceptors([ exampleInterceptorInterceptor]),
  )
]}).catch((err) => console.error(err));;

1 Comment

Please, edit and try for How to Answer, describe the effect of what you propose and explain why it helps to solve the problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.