I have an interceptor that receives a 401 (unauthorized).
Everything looks good, however while i do a single request, the interceptor gets fired twice.Very strange.
App.factory('HttpResponseInterceptor', ['$q', '$injector', '$location','ModalService', function ($q, $injector, $location, ModalService) {
return {
responseError: function (response) {
if (response.status === 401) {
console.log("401");
}
return $q.reject(response);
}
}
}]);
App.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('HttpResponseInterceptor');
}]);
I get in the chrome debugging console " 2 401", meaning it got fired twice
Here is the code that fires the network call.Its only fired once, i checked it via fiddle.
The controller:
angular.module('App')
.controller('HomeCtrl',['$scope','$http', function ($scope, $http) {
$scope.Open = function () {
$http.get('/Home/GetSum').
then(function (response) {
alert(response);
});
}
}])
and the view:
<button ng-controller="HomeCtrl" ng-click="Open()">OPEN</button>
Note that i am using Ui router.
Try remove the $q.reject(response).
If the response return 401 it is automatically rejected
Related
I'm new to angular and attempting to create a global error handler. Any time a resource is called with a 500 http status, I'd like to redirect to a generic error page.
Whenever I implement an injector, I get a "response is undefined" error. What am I doing wrong? I currently have the redirect commented out and still receive the error.
var appServices = angular.module('appServices', ['ngResource']);
appServices.factory('ApplicationService', ['$resource', function ($resource) {
return $resource('http://localhost:23357/api/application/:id', { id: '#id' }, {
update: {
method: 'PUT'
}
});
}]);
appServices.factory('globalErrorInterceptor', ['$location', '$q', '$injector', function ($location, $q, $injector) {
return {
'responseError': function (r) {
console.log('global error test');
/*
commented out for testing
if (r.status == 500) {
$location.path('/error');
}
else
return $q.reject(r);
*/
return $q.reject(r);
}
}
}]);
appServices.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('globalErrorInterceptor')
}]);
Then in my controller I call the resource like so. Currently the web service is setup to always return back a 500.
$scope.applications = ApplicationService.query();
I needed to always return the $q.reject, so the working interceptor looks like:
appServices.factory('globalErrorInterceptor', ['$location', '$q', '$injector', function ($location, $q, $injector) {
return {
'responseError': function (r) {
console.log(r);
if (r.status == 500) {
$location.path('/error');
}
return $q.reject(r);
}
}
}]);
I use AngularJS for my application and ui-route. A service in my application looks like this:
(function() {
'use strict';
angular
.module('myProject.myModule')
.factory('myService', myService);
myService.$inject = ['$http', 'api_config'];
function myService($http, api_config) {
var service = {
myServiceMethod1: myServiceMethod1,
...
};
return service;
////////////
function myServiceMethod1(params) {
return $http.get(api_config.BASE_URL + '/path');
}
Now I will implement an (global) intercetor in that way that any time a response status is HTTP 403 the interceptor should handle it.
This interceptor should be globally.
Thanks a lot!
Try something like
angular
.module('myProject.myModule')
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(['$q', '$location', function ($q, $location) {
return {
'responseError': function(response) {
if(response.status === 401 || response.status === 403) {
$location.path('/signin'); // Replace with whatever should happen
}
return $q.reject(response);
}
};
}]);
}]);
I have a factory that request user data via an api call:
angular.module('MyApp')
.factory('UserApi', function($auth,Account){
return {
getProfile: function()
{
Account.getProfile()
.then(function(response){
return response.data; ----> returning json data ok!!
});
}
}
});
But when i call the function in controller, it return me undefined
myApp.controller('AppCtrl', function($rootScope,$state,$window,$document,$scope,$filter,$resource,cfpLoadingBar,$translate,UserApi){
$scope.user = function(){
UserApi.getProfile().then(function(data){
$scope.currentUser = data;
})
}
console.log($scope.user()); ----> undefined
});
account factory:
angular.module('MyApp')
.factory('Account', function($http){
return {
getProfile: function(){
return $http.get('/api/me');
}
}
});
The error that log in console is TypeError: Cannot read property 'then' of undefined
EDIT
The only available solution is to set the response.data to $rootScope.user in which the data will be available across the controllers.
angular.module('MyApp')
.factory('UserApi', function($auth,Account,$rootScope){
return {
getProfile: function()
{
Account.getProfile()
.then(function(response){
$rootScope.user = response.data; ----> returning json data ok!!
});
return $rootScope.user;
}
}
});
First of all getProfile method should return a promise (not undefined like it's doing in your code):
angular.module('MyApp')
.factory('UserApi', function($auth,Account){
return {
getProfile: function()
{
return Account.getProfile()
.then(function(response) {
return response.data;
});
}
}
});
then in controller you should use then callback:
myApp.controller('AppCtrl', function ($rootScope, $state, $window, $document, $scope, $filter, $resource, cfpLoadingBar, $translate, UserApi) {
$scope.user = function () {
UserApi.getProfile().then(function (data) {
$scope.currentUser = data;
console.log($scope.currentUser);
})
};
});
Also make sure you understand the difference between synchronous and asynchronous code, and why console.log($scope.user()) makes no sense in this case: response if not yet available when you try to log it. Instead you provide a callback to be called when data has come.
You are trying to return the data after the request is completed successfully. But, since this is an ajax call we don't know when it will be completed (Basically, runs on a different thread.). There are two ways you can resolve this.
1 - Just return the call like so.
angular.module('MyApp')
.factory('UserApi', function($auth,Account){
return {
getProfile: function(){
return Account.getProfile(); // return call and resolve in controller.
}
}
});
2 - You can use promise ($q)
angular.module('MyApp')
.factory('UserApi', function($auth,Account, $q){
return {
getProfile: function(){
var deferred = $q.defer();
Account.getProfile()
.success(function(data){
deferred.resolve(data);
});
return deferred.promise; // just return the promise
}
}
});
and in your controller just have the following.
myApp.controller('AppCtrl', function($rootScope,$state,$window,$document,$scope,$filter,$resource,cfpLoadingBar,$translate,UserApi){
$scope.user = function(){
UserApi.getProfile().then(function(data){
$scope.currentUser = data;
console.log($scope.currentUser);
});
}
});
EDITED:
You get undefined. Because:
there isn't return in $scope.user
your console.log($scope.user($scope.user()) works only initial time.
there is time delay for getting data from UserApi.getProfile()
Also, your codes have some mistakes:
I can suggest that:
Don't use your console.log($scope.user()) initial time.
Or, You should get all data initial time when factory created. Then, you use UserApi.data in your controller.(But, there is time delay. You may get success data, if request return response before from loading of your controller).
.
angular.module('MyApp')
.factory('UserApi', function ($auth, Account) {
var data;
Account.getProfile().then(function (response) {
data = response.data;
});
return {
data: data
}
});
myApp.controller('AppCtrl', function ($rootScope, $state, $window, $document, $scope, $filter, $resource, cfpLoadingBar, $translate, UserApi) {
console.log(UserApi.data);
});
for my web page I have several angular apps. For those apps I want to create a global error handler which tracks errors with codes 500, 401 and so on and displays them as alerts.
Here is what I have so far:
I've created a global error handler module which I then inject in my apps
angular.module('globalErrorHandlerModule', [])
.factory('myHttpInterceptor', ['$rootScope', '$q', function ($rootScope, $q) {
return {
'responseError': function (rejection) {
if(rejection.status == 500){
// show error
}
return $q.reject(rejection);
}
};
}])
.config(function ($httpProvider) {
$httpProvider.interceptors.push('myHttpInterceptor');
});
angular.module('myApp', ['globalErrorHandlerModule'])
Now what I'm struggling with is actually displaying the error in an alert. What's the best way to do this? I've tried creating a separate error app and injecting the error module and share a data factory in between, but the data never gets updated in the app. Something like this:
angular.module('globalErrorHandlerModule', [])
.factory('myHttpInterceptor', ['$rootScope', '$q', 'Data', function ($rootScope, $q, Data) {
return {
'responseError': function (rejection) {
if(rejection.status == 500){
// set error
Data.error.message = '500 error';
}
return $q.reject(rejection);
}
};
}])
.factory('Data', function () {
var _error = {
message: "init"
};
return {
error: _error
};
})
.config(function ($httpProvider) {
$httpProvider.interceptors.push('myHttpInterceptor');
});
angular.module('globalErrorHandlerApp', ['globalErrorHandlerModule'])
.controller('GlobalErrorCtrl', function ($scope, Data) {
$scope.test = Data.error.message;
});
And then displaying the error as follows:
<div ng-controller="GlobalErrorCtrl">
Error {{test}}
</div>
But as mentioned I only see my initial value, and no updates to the error message. I've also tried broadcasting but that didn't work either. I'm sure there's a better way to implement something like this, I just haven't found it yet. Thanks for any tips pointing me in the right direction.
try with this
angular.module('globalErrorHandlerApp', ['globalErrorHandlerModule'])
.controller('GlobalErrorCtrl', function ($scope, Data) {
$scope.test = Data.error;
});
its a better idea watch an object than a string.
let me know if help you
<div ng-controller="GlobalErrorCtrl">
Error <span> {{test.message}} </span>
</div>
I'm trying to implement basic authentication routing in AngularJS. I have a model that has a authorize method that returns a promise. I want the routing to wait until that authorize function has returned true or false to continue, once that has completed it should resume the path or redirect the user to the login page.
I think essentially i need to stop routing, call that method and then resume or redirect to login. Below is the code I have so far but i'm not sure how to accomplish the pause/resume. Any ideas?
return angular.module('d', ['ngCookies', 'ngRoute'])
.config(['$routeProvider', '$locationProvider', '$httpProvider',
function ($routeProvider, $locationProvider, $httpProvider) {
$routeProvider.when('/',
{
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
});
$routeProvider.when('/login',
{
templateUrl: 'views/login.html',
controller: 'LoginCtrl'
});
$routeProvider.otherwise({ redirectTo: '/404' });
$locationProvider.html5Mode(true);
// handle unauthorized requests by redirecting to login page
$httpProvider.responseInterceptors.push(
['$location', '$q', function ($location, $q) {
function success(response) {
return response;
}
function error(response) {
if (response.status === 401) {
$location.path('/login');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
return function (promise) {
return promise.then(success, error);
}
}]);
}])
.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
$rootScope.$on("$routeChangeStart", function (event, next, current) {
$rootScope.error = null;
Auth.authorize().then(function(){
$location.path('/');
},function(){
$location.path('/login');
});
});
}]);
Your solution is very similar to a prototype I wrote a while back.
The idea is that whenever you "touch" the server and get an authentication error, a modal window pops-up asking for a login without changing the URL (you let it change to the new URL and stay there).
My implementation was also based on an interceptor checking for 401. It has a dependency on $rootScope and sets a property "needsLogin" to true. The page template has the login modal window visible when needsLogin === true and hides the ng-view (this is important since the new route has been loaded but it misses its data). Finally, upon successfull login, the login controller does the $route.reload() and then sets $rootScope.needsLogin = false.
Small snippets:
<div id="main" role="main" ng-show="needsLogin === false">
<div ng-view></div>
</div>
<div ng-show="needsLogin === true" ng-include="'views/login.html'" class="overlay"></div>
The login controller can be something like:
function LoginCtrl($scope, $rootScope, $route, $http) {
$scope.login = function () {
$http.post( /* Somehow do your login */ )
.success(function () {
var deregister = $rootScope.$on('$routeChangeSuccess', function () {
// hide login / show ng-view after route has been reloaded
$rootScope.needsLogin = false;
deregister();
});
$route.reload();
})
.error(function () {
/* handle errors */
});
};
}
$route.reload() is not a full page refresh, it merely re-initializes the route (controller/view etc). Hopefully, the call that was rejected before will run again and the page will be fine.