In the code below, I'd like handling errors :
401 : redirect to a login page
other : display error message (received in the message of the error)
I don't find the right way to do this.
Any idea ?
Thanks,
Module.js
var app;
(function () {
app = angular.module("studentModule", []);
})()
Service.js
app.service('StudentService', function ($http) {
this.getAllStudent = function () {
return $http.get("http://myserver/api/Student/");
}
});
Controller.js
app.controller('studentController', function ($scope, StudentService) {
function GetAllRecords() {
var promiseGet = StudentService.getAllStudent();
promiseGet.then(function (pl) { $scope.Students = pl.data },
function (errorPl) {
$log.error('Some Error in Getting Records.', errorPl);
});
}
});
As with most problems, there are many different ways to handle errors from AJAX requests in AngularJS. The easiest is to use an HTTP interceptor as already pointed out. This can handle both authentication and errors.
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(['$rootScope', '$q', function($rootScope, $q) {
return {
responseError: function(rejection) {
var deferred;
// If rejection is due to user not being authenticated
if ( rejection.status === 401 ) {
$rootScope.$broadcast('unauthenticated', rejection);
// Return a new promise since this error can be recovered
// from, like redirecting to login page. The rejection and
// and promise could potentially be stored to be re-run
// after user is authenticated.
deferred = $q.defer();
return deferred.promise;
}
$rootScope.$broadcast('serverError', rejection);
// Just reject since this probably isn't recoverable from
return $q.reject(rejection);
}
}
};
}]);
The above interceptor is created using an anonymous function but factories can be used to handle one or many different interceptors. The AngularJS docs have decent information about how to write different ones: https://docs.angularjs.org/api/ng/service/$http#interceptors
With the interceptors in place, you now just need to listen from the broadcasted events in your run method or any controller.
app.run(['$rootScope', '$location', function($rootScope, $location) {
$rootScope.$on('unauthenticated', function(response) {
// Redirect to login page
$location.path('/login');
});
$rootScope.$on('serverError', function(response) {
// Show alert or something to give feedback to user that
// server error was encountered and they need to retry
// or just display this somewhere on the page
$rootScope.serverError = response.data.errorMessage;
});
}]);
In your view:
<body ng-app="studentModule">
<div id="server_error" ng-if="!!serverError">{{serverError}}</div>
...rest of your page
</body>
Like almost all AngularJS code, most of this can be abstracted into different factories and services but this should be a good place to start.
Related
Hii I m using following code. I am reading a json file name is "users.json". If i read this file in controller through $http everything works fine. but i want to use the data that i read from file, again and again in different controller so i made a factory for this. but in factory when i read data from that json file through $http.get() and in return when i call that service method in my controller and it returns Object { $$state: Object }
app.factory('AboutFactory',['$http',function ($http) {
return {
getter: function () {
return $http({
method : 'GET',
url : '/home/penguin/Modeles/users.json',
cache : true
})
.then(function (response) {
return response.data
})
}
}
}])
Result of getter function is a promise. so you should use it like this:
AboutFactory.getter().then(function(res)
{
console.log(res);
});
That's because the $http service returns a promise as mentioned in the documentation:
The $http API is based on the deferred/promise APIs exposed by the $q
service. While for simple usage patterns this doesn't matter much, for
advanced usage it is important to familiarize yourself with these APIs
and the guarantees they provide.
You can think of a promise as if you give a top secret message to someone to deliver personally to a friend, then when delivered, report back to you with a message back from your friend.
You provide the message (the request object) to the person so that they can attempt to make the delivery of the message (send the request).
The attempted delivery has taken place (the request has been sent), it either:
a) was delivered successfully (successful response) or
b) your friend was not in so the letter could not be delivered (non success response).
You can then act depending on the response you get back
a) Message was delivered (it was a successful request) and you got a letter back (do something with the response) or
b) Message failed to get delivered (request wasn't successful), so you can maybe try again later or do something else as you don't have the information you requested
Here is an example of using the $http service with the $q service:
// app.js
(function() {
'use strict';
angular.module('app', []);
})();
// main.controller.js
(function() {
'use strict';
angular.module('app').controller('MainController', MainController);
MainController.$inject = ['AboutFactory'];
function MainController(AboutFactory) {
var vm = this;
AboutFactory.getter().then(function(data) {
// do something with your data
vm.data = data;
}, function(error) {
// give the user feedback on the error
});
}
})();
// about.service.js
(function() {
'use strict';
angular.module('app').factory('AboutFactory', AboutFactory);
AboutFactory.$inject = ['$http', '$q']
function AboutFactory($http, $q) {
var service = {
getter: getter
};
return service;
function getter() {
// perform some asynchronous operation, resolve or reject the promise when appropriate.
return $q(function(resolve, reject) {
$http({
method: 'GET',
url: 'https://httpbin.org/get',
cache: true
}).then(function(response) {
// successful status code
// resolve the data from the response
return resolve(response.data);
}, function(error) {
// error
// handle the error somehow
// reject with the error
return reject(error);
});
});
}
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<div ng-app="app" ng-controller="MainController as MainCtrl">
<pre>{{MainCtrl.data | json}}</pre>
</div>
Try this approach. It will work as per your expectation.
Read JSON file in controller through $http service as it is works fine.
For sharing the response data from one controller to another you can create a service and store the data into that service.
Service :
app.service('setGetData', function() {
var data = '';
getData: function() { return data; },
setData: function(requestData) { data = requestData; }
});
Controllers :
app.controller('myCtrl1', ['setGetData',function(setGetData) {
// To set the data from the one controller
var data = 'Hello World !!';
setGetData.setData(data);
}]);
app.controller('myCtrl2', ['setGetData',function(setGetData) {
// To get the data from the another controller
var res = setGetData.getData();
console.log(res); // Hello World !!
}]);
Here, we can see that myCtrl1 is used for setting the data and myCtrl2 is used for getting the data. So, we can share the data from one controller to another controller like this.
I'm not sure if this is a duplicate or not, but I didn't manage to find anything that worked for me, so I'm posting this question.
I have a situation where I need to get values from database before directing user to certain routes, so I could decide what content to show.
If I move e.preventDefault() right before $state.go(..) then it works, but not properly. Problem is that it starts to load default state and when it gets a response from http, only then it redirects to main.home. So let's say, if the db request takes like 2 seconds, then it takes 2 seconds before it redirects to main.home, which means that user sees the content it is not supposed to for approximately 2 seconds.
Is there a way to prevent default at the beginning of state change and redirect user at the end of state change?
Also, if we could prevent default at the beginning of state change, then how could we continue to default state?
(function(){
"use strict";
angular.module('app.routes').run(['$rootScope', '$state', '$http', function($rootScope, $state, $http){
/* State change start */
$rootScope.$on('$stateChangeStart', function(e, to, toParams, from, fromParams){
e.preventDefault();
$http
.get('/url')
.error(function(err){
console.log(err);
})
.then(function(response){
if( response.data === 2 ){
// e.preventDefault()
$state.go('main.home');
}
// direct to default state
})
}
}]);
});
You could add a resolve section to your $stateProviderConfig.
Inside the resolve you can make a request to the databse and check required conditions. If case you don't want user to acces this page you can use $state.go() to redirect him elsewhere.
Sample config:
.state({
name: 'main.home',
template: 'index.html',
resolve: {
accessGranted: ['$http', '$state', '$q',
function($http, $state, $q) {
let deffered = $q.defer();
$http({
method: 'GET',
url: '/url'
}).then(function(data) {
if (data === 2) {
// ok to pass the user
deffered.resolve(true);
} else {
//no access, redirect
$state.go('main.unauthorized');
}
}, function(data) {
console.log(data);
//connection error, redirect
$state.go('main.unauthorized');
});
return deffered.promise;
}
]
}
});
Documentation of the resolve is available here
Note that you could use Promise object instead of $q service in case you don't need to support IE
One way to handle this situation is adding an interceptor as follows.
.config(function ($httpProvider) {
$httpProvider.interceptors.push('stateChangeInterceptor');
}).factory('stateChangeInterceptor', function ($q, $window,$rootScope) {
return {
'response': function(response) {
var isValid = true;//Write your logic here to validate the user/action.
/*
* Here you need to allow all the template urls and ajax urls which doesn't
*/
if(isValid){
return response;
}
else{
$rootScope.$broadcast("notValid",{statusCode : 'INVALID'});
}
},
'responseError': function(rejection) {
return $q.reject(rejection);
}
}
})
Then handle the message 'notValid' as follows
.run(function($state,$rootScope){
$rootScope.$on("notValid",function(event,message){
$state.transitionTo('whereever');
});
})
I want to check if user is online before making call using $http in angular app, as a fallback I will get the cached data if network is not available.
Is there any option like before callback in $http do run this check?
Or maybe any other way to tackle this, I have network state & cache in localstorage
You could just write your own http service wrapper.
function httpMonkey ($http) { // I like to call all my services 'monkeys'; I find it makes angular more fun
function request (args) {
// stuff to do before, likely as a promise
.then(function () {
// the actual http request using $http
})
.then(function () {
// stuff to do after, perhaps?
});
}
var service = { request: request };
return service;
}
angular
.module('example')
.factory('HttpMonkey', httpMonkey);
You can add a custom httpInteceptor to the $httpProvider service in angularJs. As an example below - I have created an httpInteceptor which will show loadingSpinner before each $http call and hide it after success/error.
//Intercepts ALL angular ajax http calls
app.factory('httpInterceptor', function ($q, $rootScope, $log) {
var numLoadings = 0;
return {
request: function (config) {
numLoadings++;
// Show loader
$('#loadingSpinner').show();
return config || $q.when(config)
},
response: function (response) {
if ((--numLoadings) === 0) {
// Hide loader
$('#loadingSpinner').hide();
}
return response || $q.when(response);
},
responseError: function (response) {
if (!(--numLoadings)) {
// Hide loader
$('#loadingSpinner').hide();
}
return $q.reject(response);
}
};
})
and then push this interceptor to the $httpProvider.interceptors in your app.config-
app.config(function ($routeProvider, $httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
.
.
});
I am making a simple get request using $http .and it fails with 503-service unavailable error which is clearly shown in the network tab .But the rejection object in the responseError interceptor object shows status: 0.
Here is my Interceptor
angular.module("app", []).config(function($httpProvider){
$httpProvider.interceptors.push(function($q) {
return {
'responseError': function(rejection) {
console.log(rejection);
}
}
})
});
I am expecting status code 503 in the interceptor but i am getting 0.Please help me to understand and resolve the issue.
Here is a fiddle illustrating the issue.
The request is a cross domain request.
The service you are using does return any data. Angular $http expects some data to be returned, and in this case "GetStatusCode" just stops and does not return anything.
Also, fyi - your fiddle does not respect angular's method of binding to a controller, which expects a string for the controller name.
See http://plnkr.co/edit/x46D5FIsgaKsaSocYjpz?p=preview for proper markup.. where we name the controller as a string, for ex:
angular.module("plunker", [])
.config(function ($httpProvider) {
$httpProvider.interceptors.push(function () {
return {
'responseError': function (rejection) {
console.log('rejection = ', rejection);
},
'response': function (response) {
console.log('response = ', response);
}
}
})
})
.controller('Controller', function Controller($scope, $http) {
$scope.getCode = function () {
var req = 'http://www.reddit.com/r/catpictures.json?limit=50&jsonp=JSON_CALLBACK';
return $http.jsonp(req);
};
$scope.get = function () {
return $scope.getCode()
};
})
;
I am trying to use resolve in a $routeProvider to display the new route only when a $http request is finished. If the request is successful, the promise resulting from the $http.post() is resolved and the view is rendered. But if the request fails (timeout or internal error for eg.), the promise is never resolved and the the view is never rendered. How can I deal with request failure using resolve ?
The most important parts of the code is bellow :
app.js
$routeProvider.when('/warrantyResult', {
templateUrl : 'partials/warranty-result.html',
controller : 'WarrantyResultCtrl',
resolve : {
response : [ 'Warranty', function(Warranty) {
return Warranty.sendRequest();
} ]
}
});
controllers.js
angular.module('adocDemo.controllers', []).controller('HomeCtrl', [ '$scope', function($scope) {
} ]).controller('WarrantyCtrl', [ '$scope', '$http', '$location', 'Warranty', function($scope, $http, $location, Warranty) {
$scope.submitWarranty = function() {
$scope.loading = true;
Warranty.setRequestData($scope.data);
$location.path('/warrantyResult');
};
} ]).controller('WarrantyResultCtrl', [ '$scope', 'Warranty', function($scope, Warranty) {
$scope.request = Warranty.getRequestData();
$scope.response = Warranty.getResponseData();
} ]);
services.js
angular.module('adocDemo.services', []).factory('Warranty', [ '$http', '$timeout', function($http, $timeout) {
/**
* This service is used to query the Warranty Webmethod. The sendRequest
* method is automaticcaly called when the user is redirected to
* /warrantyResult route.
*/
var isDataSet = false;
var requestData = undefined;
var responseData = undefined;
return {
setRequestData : function(data) {
//Setting the data
isDataSet = true;
},
getRequestData : function() {
return requestData;
},
sendRequest : function(data) {
if(isDataSet) {
var request = $http.post('url/to/webservice', requestData);
request.success(function(data) {
responseData = data;
});
return request;
}
},
getResponseData : function() {
return responseData;
}
};
} ]);
I know i could use a promise around the $http call and resolve it even if the request is a failure, but I'm wondering if there is a simpler solution.
Thanks for reading and, hopefully, helping :)
I think the only way to do it from resolve is to manually resolve the promise returned by Warranty.sendRequest and rewrap it in a new promise:
resolve : {
response : [ 'Warranty' '$q', function(Warranty, $q) {
var dfd = $q.defer();
Warranty.sendRequest().then(function(result) {
dfd.resolve({ success: true, result : result });
}, function(error) {
dfd.resolve({ success : false, reason : error });
});
return dfd.promise;
} ]
}
In WarrantyResultCtrl, you could check if an error occurred and generate a redirect.
EDIT: much cleaner solution:
// WarrantyCtrl
$scope.$on('$routeChangeError', function() {
// handle the error
});
$scope.submitWarranty = function() {
$scope.loading = true;
Warranty.setRequestData($scope.data);
$location.path('/warrantyResult');
};
(plunker demo)
What I have found is that the controller is not fired at all if the Promise is rejected, and your view is never rendered--same as you.
What I also discovered is that if you handle the Promise with a .then() in your $routeProvider's resolve, the .then() will return a new Promise that is resolved and your controller is fired after all, albeit without the data you are expecting.
For example:
$routeProvider.when('/warrantyResult', {
templateUrl : 'partials/warranty-result.html',
controller : 'WarrantyResultCtrl',
resolve : {
response : [ 'Warranty', function(Warranty) {
return Warranty.sendRequest()
.then(null, function(errorData) {
// Log an error here?
// Or do something with the error data?
});
}]
}
});
Now in your controller you will want to check whether response is undefined. If it is undefined then you'll know that the call to Warranty.sendRequest() failed, and you can act accordingly.
For what it's worth, I did not go this route. Instead, I injected the $location service into the resolve handler and redirected to an error page if the $http call gets rejected.
UPDATE
Just noticed that in your controller you are injecting the Warranty service when you should instead be injecting the response that you defined in your resolve. That will prevent your view from rendering until the Promise returned from Warranty.sendRequest() is resolved.
After deep searching, I could not find a solution for this problem.
I decided to drop the resolve statment in my route definition and I use the following snippet of code in the WarrantyCtrl.
$scope.submitWarranty = function() {
$scope.formatUserData();
if ($scope.verifyUserData()) {
$scope.loading = true;
Warranty.setRequestData($scope.data);
Warranty.sendRequest().success(function() {
$location.path('/warrantyResult');
}).error(function() {
$location.path('/webserviceError');
});
}
};
Not very clever, but works as intented ... If someone still have the solution for the original problem, I would be very pleased to read it !