AngularJS: iniezione del servizio in un intercettore HTTP (dipendenza circolare)

Sto cercando di scrivere un intercettore HTTP per la mia app AngularJS per gestire l’autenticazione.

Questo codice funziona, ma sono preoccupato di iniettare manualmente un servizio poiché pensavo che Angular avrebbe dovuto gestirlo automaticamente:

app.config(['$httpProvider', function ($httpProvider) { $httpProvider.interceptors.push(function ($location, $injector) { return { 'request': function (config) { //injected manually to get around circular dependency problem. var AuthService = $injector.get('AuthService'); console.log(AuthService); console.log('in request interceptor'); if (!AuthService.isAuthenticated() && $location.path != '/login') { console.log('user is not logged in.'); $location.path('/login'); } return config; } }; }) }]); 

Quello che ho iniziato a fare, ma ho incontrato problemi circolari di dipendenza:

  app.config(function ($provide, $httpProvider) { $provide.factory('HttpInterceptor', function ($q, $location, AuthService) { return { 'request': function (config) { console.log('in request interceptor.'); if (!AuthService.isAuthenticated() && $location.path != '/login') { console.log('user is not logged in.'); $location.path('/login'); } return config; } }; }); $httpProvider.interceptors.push('HttpInterceptor'); }); 

Un altro motivo per cui sono preoccupato è che la sezione su $ http in Angular Docs sembra mostrare un modo per ottenere le dipendenze iniettate la “via normale” in un intercettore Http. Vedi il loro frammento di codice sotto “Interceptors”:

 // register the interceptor as a service $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { return { // optional method 'request': function(config) { // do something on success return config || $q.when(config); }, // optional method 'requestError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); }, // optional method 'response': function(response) { // do something on success return response || $q.when(response); }, // optional method 'responseError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); }; } }); $httpProvider.interceptors.push('myHttpInterceptor'); 

Dove dovrebbe andare il codice sopra?

Credo che la mia domanda sia: qual è il modo giusto per farlo?

Grazie, e spero che la mia domanda sia stata abbastanza chiara.

Hai una dipendenza circolare tra $ http e il tuo AuthService.

Quello che stai facendo usando il servizio $injector sta risolvendo il problema della gallina e dell’uovo ritardando la dipendenza di $ http su AuthService.

Credo che quello che hai fatto sia in realtà il modo più semplice per farlo.

Puoi anche farlo:

  • Registrare l’intercettore più tardi (farlo in un blocco run() invece di un blocco config() potrebbe già fare il trucco). Ma puoi garantire che $ http non sia già stato chiamato?
  • “Iniettando” $ http manualmente in AuthService quando si registra l’intercettatore chiamando AuthService.setHttp() o qualcosa del genere.

Questo è quello che ho finito per fare

  .config(['$httpProvider', function ($httpProvider) { //enable cors $httpProvider.defaults.useXDomain = true; $httpProvider.interceptors.push(['$location', '$injector', '$q', function ($location, $injector, $q) { return { 'request': function (config) { //injected manually to get around circular dependency problem. var AuthService = $injector.get('Auth'); if (!AuthService.isAuthenticated()) { $location.path('/login'); } else { //add session_id as a bearer token in header of all outgoing HTTP requests. var currentUser = AuthService.getCurrentUser(); if (currentUser !== null) { var sessionId = AuthService.getCurrentUser().sessionId; if (sessionId) { config.headers.Authorization = 'Bearer ' + sessionId; } } } //add headers return config; }, 'responseError': function (rejection) { if (rejection.status === 401) { //injected manually to get around circular dependency problem. var AuthService = $injector.get('Auth'); //if server returns 401 despite user being authenticated on app side, it means session timed out on server if (AuthService.isAuthenticated()) { AuthService.appLogOut(); } $location.path('/login'); return $q.reject(rejection); } } }; }]); }]); 

Nota: le chiamate $injector.get devono rientrare nei metodi dell’intercettatore, se si tenta di utilizzarle altrove, si continuerà a ottenere un errore di dipendenza circolare in JS.

Penso che usare $ injector direttamente sia un antipattern.

Un modo per interrompere la dipendenza circolare è utilizzare un evento: invece di iniettare $ state, iniettare $ rootScope. Invece di redirect direttamente, fallo

 this.$rootScope.$emit("unauthorized"); 

più

 angular .module('foo') .run(function($rootScope, $state) { $rootScope.$on('unauthorized', () => { $state.transitionTo('login'); }); }); 

La ctriggers logica ha reso tali risultati

In realtà non c’è alcun senso di cercare è utente creato o non in Http Interceptor. Vorrei raccomandare di avvolgere tutte le richieste HTTP in singolo .service (o .factory, o in .provider), e usarlo per TUTTE le richieste. Ogni volta che si chiama la funzione, è ansible verificare se l’utente ha effettuato l’accesso o meno. Se tutto è ok, consenti la richiesta di invio.

Nel tuo caso, l’applicazione Angolare invierà la richiesta in ogni caso, è sufficiente controllare l’authorization lì, e dopo che JavaScript invierà la richiesta.

Core del tuo problema

myHttpInterceptor è chiamato in istanza $httpProvider . Il tuo AuthService utilizza $http , o $resource , e qui hai ricorsione di dipendenza o dipendenza circolare. Se rimuovi quella dipendenza da AuthService , allora non vedrai AuthService .


Come indicato da @Pieter Herroelen, puoi inserire questo intercettore nel modulo module.run , ma questo sarà più simile a un hack, non a una soluzione.

Se vuoi fare un codice pulito e auto descrittivo, devi seguire alcuni dei principi SOLID.

Almeno il principio della responsabilità unica ti aiuterà molto in queste situazioni.

Se stai solo verificando lo stato Auth (isAuthorized ()), ti consiglio di inserire tale stato in un modulo separato, ad esempio “Auth”, che mantiene lo stato e non usa $ http stesso.

 app.config(['$httpProvider', function ($httpProvider) { $httpProvider.interceptors.push(function ($location, Auth) { return { 'request': function (config) { if (!Auth.isAuthenticated() && $location.path != '/login') { console.log('user is not logged in.'); $location.path('/login'); } return config; } } }) }]) 

Modulo di autenticazione:

 angular .module('app') .factory('Auth', Auth) function Auth() { var $scope = {} $scope.sessionId = localStorage.getItem('sessionId') $scope.authorized = $scope.sessionId !== null //... other auth relevant data $scope.isAuthorized = function() { return $scope.authorized } return $scope } 

(Ho usato localStorage per memorizzare il sessionId sul lato client qui, ma puoi anche impostarlo all’interno del tuo AuthService dopo una chiamata $ http per esempio)