ecmascript - angularjs http post example with parameters
La clase de interceptor http angularjs(ES6) pierde la vinculaciĆ³n con ''this'' (9)
El contexto ( this
) se pierde porque el marco Angular solo mantiene las referencias a las propias funciones del controlador, y las invoca directamente sin ningún contexto, como ha señalado alexpods .
Hace poco escribí una publicación en el blog sobre cómo escribir interceptores $http
usando TypeScript, que también se aplica a las clases de ES6: Interceptores AngularJS 1.x que usan TypeScript .
Para resumir lo que he discutido en esta publicación, para no perder this
en sus manejadores, tendrá que definir los métodos como funciones de flecha, colocando las funciones directamente dentro de la función constructor
de la clase en el código ES5 compilado.
class AuthenticationInterceptor {
/* ngInject */
constructor($q, $window) {
this.$q = $q;
this.$window = $window;
}
responseError = (rejection) => {
var authToken = rejection.config.headers.Authorization;
if (rejection.status === 401 && !authToken) {
let authentication_url = rejection.data.errors[0].data.authenticationUrl;
this.$window.location.replace(authentication_url);
return this.$q.defer(rejection);
}
return this.$q.reject(rejections);
}
}
Si realmente insiste en que su interceptor esté escrito como una clase totalmente basada en prototipos, puede definir una clase base para su interceptor y extenderla. La clase base reemplazaría las funciones de prototipo de interceptor con métodos de instancia, por lo que podemos escribir nuestros interceptores de la siguiente manera:
class HttpInterceptor {
constructor() {
[''request'', ''requestError'', ''response'', ''responseError'']
.forEach((method) => {
if(this[method]) {
this[method] = this[method].bind(this);
}
});
}
}
class AuthenticationInterceptor extends HttpInterceptor {
/* ngInject */
constructor($q, $window) {
super();
this.$q = $q;
this.$window = $window;
}
responseError(rejection) {
var authToken = rejection.config.headers.Authorization;
if (rejection.status === 401 && !authToken) {
let authentication_url = rejection.data.errors[0].data.authenticationUrl;
this.$window.location.replace(authentication_url);
return this.$q.defer(rejection);
}
return this.$q.reject(rejections);
}
}
Estoy construyendo y la aplicación AngularJS utilizando clases ES6 con transpilión de traceur a ES5 en formato AMD.
en mi módulo importo la clase de interceptor y lo registro como un servicio, y luego registro este servicio con los $ httpProvider.interceptors en module.config:
var commonModule = angular.module(moduleName, [constants.name]);
import authenticationInterceptor from ''./authentication/authentication.interceptor'';
commonModule.service(''authenticationInterceptor'', authenticationInterceptor);
commonModule.config( $httpProvider => {
$httpProvider.interceptors.push(''authenticationInterceptor'');
});
Mi clase de interceptor inyecta los servicios $ q y $ window , los guarda en el constructor para su uso posterior. Seguí esta parte con el depurador y la inyección se está realizando correctamente:
''use strict'';
/*jshint esnext: true */
var authenticationInterceptor = class AuthenticationInterceptor {
/* ngInject */
constructor($q, $window) {
this.$q = $q;
this.$window = $window;
}
responseError(rejection) {
var authToken = rejection.config.headers.Authorization;
if (rejection.status === 401 && !authToken) {
let authentication_url = rejection.data.errors[0].data.authenticationUrl;
this.$window.location.replace(authentication_url);
return this.$q.defer(rejection);
}
return this.$q.reject(rejections);
}
}
authenticationInterceptor.$inject = [''$q'', ''$window''];
export default authenticationInterceptor;
Cuando hago una solicitud que responde con un 401, el interceptor se activa adecuadamente, pero en el método ''responseError'' la variable ''this'' apunta al objeto de la ventana y no a mi interceptor, por lo tanto no tengo acceso a este. $ Q o esta. $ ventana .
¿No puedo entender por qué? ¿Algunas ideas?
Este es exactamente el mismo problema que estoy experimentando, sin embargo, encontré una solución al configurar ''this'' en una variable propia, igual que resolver el problema de alcance en es5, y funciona bien:
let self;
class AuthInterceptor{
constructor(session){
self = this;
this.session = session;
}
request(config){
if(self.session) {
config.headers = self.session.getSessionParams().headers;
}
return config;
}
responseError(rejection){
if(rejection.status == 401){
}
return rejection;
}
}
export default AuthInterceptor;
Mi solución de trabajo sin usar ngInject
myInterceptor.js
export default ($q) => {
let response = (res) => {
return res || $q.when(res);
}
let responseError = (rejection) => {
//do your stuff HERE!!
return $q.reject(rejection);
}
return {
response: response,
responseError: responseError
}
}
myAngularApp.js
// angular services
import myInterceptor from ''myInterceptor'';
// declare app
const application = angular.module(''myApp'', [])
.factory(''$myInterceptor'', myInterceptor)
.config([''$httpProvider'', function($httpProvider) {
$httpProvider.interceptors.push(''$myInterceptor'');
}]);
Mira estas líneas de código fuente :
// apply interceptors
forEach(reversedInterceptors, function(interceptor) {
if (interceptor.request || interceptor.requestError) {
chain.unshift(interceptor.request, interceptor.requestError);
}
if (interceptor.response || interceptor.responseError) {
chain.push(interceptor.response, interceptor.responseError);
}
});
Cuando el método interceptor.responseError
se inserta en la cadena, pierde su contexto (solo se empuja la función, sin ningún contexto);
Más adelante here se agregará para prometer como rechazar la devolución de llamada:
while (chain.length) {
var thenFn = chain.shift();
var rejectFn = chain.shift();
promise = promise.then(thenFn, rejectFn);
}
Por lo tanto, si la promesa será rechazada, rejectFn
(su función responseError
) se ejecutará como una función ordinaria. En este caso, this
referencia a la window
si el script se está ejecutando en modo no estricto o, de lo contrario, es null
.
IMHO Angular 1 fue escrito con consideración de ES5, así que creo que usarlo con ES6 no es una buena idea.
Para agregar a la conversación, puede devolver un objeto del constructor que contiene métodos de clase enlazados explícitamente.
export default class HttpInterceptor {
constructor($q, $injector) {
this.$q = $q;
this.$injector = $injector;
return {
request: this.request.bind(this),
requestError: this.requestError.bind(this),
response: this.response.bind(this),
responseError: this.responseError.bind(this)
}
}
request(req) {
this.otherMethod();
// ...
}
requestError(err) {
// ...
}
response(res) {
// ...
}
responseError(err) {
// ...
}
otherMethod() {
// ...
}
}
Para compeler las otras respuestas finas relacionadas con las funciones de flecha, creo que es un poco más limpio usando un método de fábrica estático en el Interceptor:
export default class AuthenticationInterceptor {
static $inject = [''$q'', ''$injector'', ''$rootRouter''];
constructor ($q, $injector, $rootRouter) {
this.$q = $q;
this.$injector = $injector;
this.$rootRouter = $rootRouter;
}
static create($q, $injector, $rootRouter) {
return new AuthenticationInterceptor($q, $injector, $rootRouter);
}
responseError = (rejection) => {
const HANDLE_CODES = [401, 403];
if (HANDLE_CODES.includes(rejection.status)) {
// lazy inject in order to avoid circular dependency for $http
this.$injector.get(''authenticationService'').clearPrincipal();
this.$rootRouter.navigate([''Login'']);
}
return this.$q.reject(rejection);
}
}
Uso:
.config([''$provide'', ''$httpProvider'', function ($provide, $httpProvider) {
$provide.factory(''reauthenticationInterceptor'', AuthenticationInterceptor.create);
$httpProvider.interceptors.push(''reauthenticationInterceptor'');
}]);
Solución de trabajo con funciones de flecha:
var AuthInterceptor = ($q, $injector, $log) => {
''ngInject'';
var requestErrorCallback = request => {
if (request.status === 500) {
$log.debug(''Something went wrong.'');
}
return $q.reject(request);
};
var requestCallback = config => {
const token = localStorage.getItem(''jwt'');
if (token) {
config.headers.Authorization = ''Bearer '' + token;
}
return config;
};
var responseErrorCallback = response => {
// handle the case where the user is not authenticated
if (response.status === 401 || response.status === 403) {
// $rootScope.$broadcast(''unauthenticated'', response);
$injector.get(''$state'').go(''login'');
}
return $q.reject(response);
}
return {
''request'': requestCallback,
''response'': config => config,
''requestError'': requestErrorCallback,
''responseError'': responseErrorCallback,
};
};
/***/
var config = function($httpProvider) {
$httpProvider.interceptors.push(''authInterceptor'');
};
/***/
export
default angular.module(''services.auth'', [])
.service(''authInterceptor'', AuthInterceptor)
.config(config)
.name;
Tenga en cuenta que el uso de las funciones de flecha en las propiedades de clase es una función experimental para ES7. Sin embargo, la mayoría de los transpilers no tienen ningún problema con eso.
Si desea mantener la implementación oficial de ES6, puede crear métodos de instancia en lugar de métodos prototipo definiendo sus métodos en el constructor.
class AuthenticationInterceptor {
/* ngInject */
constructor($q, $window) {
this.responseError = (rejection) => {
const authToken = rejection.config.headers.Authorization;
if (rejection.status === 401 && !authToken) {
const authentication_url = rejection.data.errors[0].data.authenticationUrl;
$window.location.replace(authentication_url);
return $q.defer(rejection);
}
return $q.reject(rejection);
};
}
}
Me gusta esta solución porque disminuye la cantidad de código repetitivo;
- Ya no tienes que poner todas tus dependencias en
this
. Así que en lugar de usarthis.$q
puedes usar$q
. - No hay necesidad de devolver métodos de clase explícitamente vinculados desde el constructor
Tener un nivel adicional de sangría es una desventaja. Además, este método podría no ser adecuado para las clases que se crean muchas instancias, ya que consume más memoria en ese caso. P.ej; El uso de propiedades de clase directas (transpiladas a métodos de prototipo) es más eficiente para los controladores de componentes que probablemente se usarán varias veces en una página. No se preocupe por los servicios, proveedores y fábricas, ya que estos son todos singletons y solo serán instanciados una vez.
export default class AuthInterceptor{
/*@ngInject;*/
constructor(SomeService,$q){
this.$q=$q;
this.someSrv = SomeService;
this.request = (config) =>{
...
this.someSrv.doit();
return config;
}
this.response = (response)=>{
...
this.someSrv.doit();
return response;
}
this.responseError = (response) => {
...
return this.$q.reject(response);
}
}
}