Je reçois le comportement suivant d'injection de dépendance étrange lorsque j'utilise des HttpInterceptors personnalisés dans Angular 5+.
Le code simplifié suivant fonctionne bien:
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = this.auth.getToken();
return next.handle(req);
}
}
export class AuthService {
token: string;
constructor() {
console.log('AuthService.constructor');
}
}
TOUTEFOIS....
Lorsque la AuthService
a une ou plusieurs dépendances à elle seule, par ex.
export class AuthService {
token: string;
constructor(private api: APIService) {
console.log('AuthService.constructor');
}
}
angular tente de créer à plusieurs reprises de nouvelles instances de AuthService
jusqu'à ce que je reçoive les erreurs suivantes:
Le journal affiche le message AuthService.constructor
environ 400 fois.
et
Cannot instantiate cyclic dependency! HTTP_INTERCEPTORS ("[ERROR ->]"): in NgModule AppModule
et
app.component.html: 44 ERREUR RangeError: Taille maximale de la pile d'appels dépassé
J'ai ensuite essayé d'injecter le service en utilisant la classe Injector -
export class AuthService {
token: string;
api: APIService;
constructor(private injector: Injector) {
this.api = this.injector.get(APIService);
console.log('AuthService.constructor');
}
}
mais obtenant la même erreur (taille maximale de la pile d'appels).
APIService
est un service simple qui n'injecte que HttpClient
dans son constructeur.
@Injectable()
export class APIService {
constructor(private http: HttpClient) {}
}
Enfin, lorsque j'injecte AuthService
dans l'Interceptor à l'aide de Injector
, l'erreur disparaît mais le service AuthService est instancié plus de 200 fois:
export class AuthInterceptor implements HttpInterceptor {
auth: AuthService;
constructor(private injector: Injector) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.auth = this.auth || this.injector.get(AuthService);
const token = this.auth.getToken();
return next.handle(req);
}
}
En regardant la documentation officielle et d'autres exemples, il semble qu'il est techniquement possible d'injecter des services dans les intercepteurs Http. Existe-t-il une limitation ou une autre configuration manquante?
Donc, il s'avère que si le service que vous injectez dans Http Interceptor a une dépendance sur HttpClient
, cela conduit à une dépendance cyclique.
Puisque ma AuthService
était un mélange de différentes logiques (connexion/déconnexion, routage de l'utilisateur, sauvegarde/chargement de jetons, appels api), j'ai séparé la partie nécessaire aux intercepteurs dans son propre service (informations d'identification et jetons uniquement) et maintenant l'injecter avec succès dans l'intercepteur.
export class AuthInterceptor implements HttpInterceptor {
constructor(private credentials: CredentialsService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = this.credentials.getToken();
const api_key = this.credentials.getApiKey();
}
}
export class CredentialsService {
token: string;
user: IUser;
constructor(private http: HttpClient) {
this.loadCredentialsFromStorage();
}
}
Cela semble bien fonctionner. J'espère que ça aide quelqu'un.
Angular Team a résolu ce problème dans Angular 5.2.3 publié le 31 janvier 2018. Après la mise à jour de la version angulaire, vous pourrez injecter des services utilisant HTTPClient comme d'habitude dans le constructeur.
Corrections de bugs
common: permet à HttpInterceptors d'injecter HttpClient (# 19809) (ed2b717), ferme # 18224
vous devez ajouter Injector
dans le constructeur et injecter AuthService
via un injecteur
export class AuthInterceptor implements HttpInterceptor {
constructor(private inj: Injector) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const auth = this.inj.get(AuthService);
const token = this.auth.getToken();
return next.handle(req);
}
}
n'oubliez pas l'importation
import {Injector} from '@angular/core';
J'ai eu un problème similaire avec la même conception d'un service d'authentification couplé à un intercepteur.
@Injectable() AuthInterceptorService {
constructor (authApi: AuthApiService) {}
handle () {...do the job}
}
@Injectable() AuthApiService {
constructor () {
// ...do some setup with a request, such as to get current session
// that leads to an indirect circular between 2 constructors.
}
}
Dans mon cas, j'ai trouvé la cause, c'est que j'essaie de démarrer une requête http dans le constructeur du service d'authentification. À ce stade, l'injecteur semble ne pas avoir terminé l'enregistrement de l'instance du service d'autorisation, tandis que le client http capture la nouvelle demande et tente à nouveau d'instantanéiser l'intercepteur, car l'instance précédente de l'intercepteur était également bloquée dans son constructeur sur la pile d'appels!
Cette invocation récursive avec deux constructeurs, casse le motif singleton de l'injecteur et conduit à une pile d'absence d'appel.