I am using Parse.com as a backend for an Ionic Framework App which means I have to monitor the network activity. I can handle online / offline by using this guide. However, I need a more 'app wide' solution checks on each call. I'm currently getting errors when there is no network as the response from the server is null causing the following error:
Error: null is not an object (evaluating 'response.error')
My question is two fold:
Is it possible to create a network factory / service that I can inject into my ParseFactory to check and notify the user that their network is insufficient.
Handle a null response object in this code:
ParseFactory.provider('Programme/').get($stateParams.loadProgramme).success (function(data){
// do something with success...
}).error(function(response){
$rootScope.$emit('errorEvent',
{"message" : "Unable to load Programme. Please check your connection", "errorObject": response.error}
);
});
My Factory for Parse calls is below:
.factory('ParseFactory',['$http','PARSE_CREDENTIALS',function($http,PARSE_CREDENTIALS){
var baseUrl = 'https://api.parse.com/1/classes/';
return {
provider:function(type, queryParam, queryValue) {
console.log(queryValue);
whereQuery = {}
whereQuery[queryParam] = queryValue;
var masterQuery = {'userId': masterKey};
return {
getAll:function(){
return $http.get(getUrl(type),getParams(whereQuery));
},
getMasters:function(){
return $http.get(getUrl(type),getParams(masterQuery));
},
get:function(id){
return $http.get(getUrl(type)+id,getParams());
},
create:function(data){
return $http.post(getUrl(type),data,getParams());
},
edit:function(id,data){
return $http.put(getUrl(type)+id,data,getParams());
},
delete:function(id){
return $http.delete(getUrl(type)+id,getParams());
}
}
function getUrl(type) {
return baseUrl+type;
}
function getParams(user) {
return {
timeout : 5000,
headers:{
'X-Parse-Application-Id': PARSE_CREDENTIALS.APP_ID,
'X-Parse-REST-API-Key':PARSE_CREDENTIALS.REST_API_KEY,
'X-Parse-Session-Token': Parse.User.current()._sessionToken,
'Content-Type':'application/json'
},
params: {
where: user,
// limit: 2,
// count: 1
// include: "something"
}
}
}
}
}
}])
Use a $http interceptor. All requests using $http are passed through interceptors, so by using interceptors you can apply global handling for successful or unsuccessful requests and responses.
For example, this interceptor cancels a request if the network connection is not available and emits an event when an empty response is received:
app.factory('networkActivityInterceptor', function($rootScope, $q, networkMonitorService) {
return {
'request': function(config) {
var canceller = $q.defer();
config.timeout = canceller.promise;
if (!networkMonitorService.hasNetworkConnection()) {
// cancels the request if there is no network connection
canceller.resolve();
}
// otherwise, let the request go through as normal
return config;
},
'response': function(response) {
// handle a null/empty response
if (!response.data) {
$rootScope.$emit('errorEvent', {
message: 'Unable to load...',
errorObject: response.error
});
}
return response;
},
'responseError': function(response) {
// the response handling above could also be done in the responseError
// handler which is hit when an error HTTP code is returned. (example: 404, 500)
// this depends on what your server is configured to return.
// note that in a responseError handler you must return a rejected promise
// (i.e. return $q.reject(response);)
}
};
});
Substitute networkMonitorService.hasNetworkConnection() for your app's network activity logic.
Related
I do not have the ability to change an endpoint, and it will return a 200 OK response with different data if it fails. How can I make it run the error function (The 2nd function in a then from a promise)?
MyService service:
self.getData = function() {
return $http.get('/api/controller/action').then(function(x) {
//This will be run even on failure. How can I call....
return x.data;
}, function(x) {
//......here, from the front-end, so that, the 2nd function in
//inside the 'then' in the controller is run.
//This code will currently never run as it never fails server side.
return x;
});
};
controller:
MyService.getData().then(function(x) {
//Success
}, function(x) {
//Failure
});
Use $q.reject, eg
return $http.get('/api/controller/action').then(function(x) {
if (x.data === 'some error condition') {
return $q.reject(x);
}
you can also check status of response you are getting after hitting the API ,
like, means there is some problem in API
if(status!=200){
// do something
}
I am using the following code with ngResource to retrieve a list of objects:
// Create the 'articles' service
angular.module('articles').factory('Articles', ['$resource', function($resource) {
// Use the '$resource' service to return an article '$resource' object
return $resource('../api/admins/:adminId/articles/:articleId', {
adminId: '#adminId'
}, {
update: {
method: 'PUT'
}
});
}]);
Articles are retrieved like so:
$scope.list = function() {
// Use the articles'query' method to send an appropriate GET request
Articles.query(
function(articles){
$scope.data.articles= articles;
},
function(error){
console.log(error);
}
);
};
When a user is logged in, all works fine: The client expects an array and that's what it gets.
But after a while when the login timed out, the server will return a 401 error with an object instead of an array. In fact, this is EXACTLY what is supposed to happen, but Angular throws the following error:
Error: [$resource:badcfg] Error in resource configuration.
Expected response to contain an array but got an object
The same problem occurs when a user retrieves a page that is forbidden (403).
Is there a way to resolve 401 and 403 request errors without getting an actual javascript error in Angular?
the query action of $resource by default expects an array to be returned from the server (see the docs).
You could use the transformResponse option for the query action to compensate for this like so:
return $resource('../api/admins/:adminId/articles/:articleId', {
adminId: '#adminId'
}, {
update: {
method: 'PUT'
},
query: {
transformResponse: function(data, headers) {
if(!angular.isArray(data)) {
return new Array(data);
}
return data;
}
}
});
Of course it would be much better to handle errors using the error callback or with an interceptor
.factory('ChartService', ['$http','$q',
function ChartService($http,$q) {
// interface
// implementation
var canceler = $q.defer();
function getTableData() {
return $http.post('http://202.429.115.52:9906/oo/api.php?request=getSubfunctionWiseHCAndSW').success(function (data) {
if (data.mm == "No Data Available"){
localData();
}
return data;
}).error(function(error){
alert("error")
canceler.resolve();
localData()
// console.log(error)
});
}
function localData(){
alert("loaddata")
return $http.get('vro/hcswc.json').success(function(response){
console.log(response+"==");
return response;
}).error(function(error){
console.log(error);
});
}
return {
getTableData:getTableData
}
}
]);
Error
XMLHttpRequest cannot load http://192.127.215.52:9906/api.php?request=getSubfunctionrection. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:63342' is therefore not allowed access. The response had HTTP status code 404.
App.js // resolve Code
.state('app.vro', {
url: "/vro/:isfirstActiveState",
views: {
'menuContent': {
controller: "vrobCtrl",
templateUrl: 'vrob/vrob.html'
}
} ,
resolve: {
tableData:function(ChartService){
return ChartService.getTableData();
}
}
})
Hello
I am trying to get data form service .I have one condition while calling webservice .the condition is if I will get error then I get request from local in other words if I get any error from server than I need to read json file from local . I need to use resolve and call my service and use that data on controller .my localData is not returning data to resolve .what is the problem in my code ..
here is one example
http://plnkr.co/edit/0y9V0m2hmsUBRXoeyjig?p=preview
I am getting error correct but it should load data from local json .why it is not loading data from local json
It is an anti-pattern to use $q to manage $http requests but in this situation I don't know another way:
function getTableData() {
var deferred = $q.defer();
$http.post('http://202.129.215.52:9906/mondelez/api.php?request=getSubfunctionWiseHCAndSW')
.then(
// resolve callback
function(data) {
deferred.resolve(data.data)
return data.data;
},
// reject callback make different request
function(err) {
$http.get('data.json').success(function(response) {
deferred.resolve(response);
}).error(function(error) {
deferred.reject()
});
})
return deferred.promise;
}
DEMO
Throughout my project I have a piece of code that performs CRUD with Parse.com via the REST API. I would like to insert a function in the error handler that is a universal (project wide) error function.
I have considered a factory / service or possibly passing something via $rootScope.
Secondly I would like to wrap all my api calls in a timeout function so that if there is no response in a certain time then I can notify the user with a useful notification.
ParseFactory.provider('Programme').getAll().success(function(data){
$scope.programmes = data.results;
}).error(function(response){
$ionicPopup.alert({
title: 'Unable to load - Please check your connection '
});
});
Any examples of how to best achieve this would be greatly received.
=== EDIT - adding ParseFactory Code ===
.factory('ParseFactory',['$http','PARSE_CREDENTIALS',function($http,PARSE_CREDENTIALS){
var baseUrl = 'https://api.parse.com/1/classes/';
return {
provider:function(type) {
var userQuery = {'userId': Parse.User.current().id};
return {
getAll:function(){
return $http.get(getUrl(type),getUserParams());
},
get:function(id){
return $http.get(getUrl(type)+id,getParams());
},
create:function(data){
return $http.post(getUrl(type),data,getParams());
},
edit:function(id,data){
return $http.put(getUrl(type)+id,data,getParams());
},
delete:function(id){
return $http.delete(getUrl(type)+id,getParams());
}
}
function getUrl(type) {
return baseUrl+type;
}
function getParams() {
return {
headers:{
'X-Parse-Application-Id': PARSE_CREDENTIALS.APP_ID,
'X-Parse-REST-API-Key':PARSE_CREDENTIALS.REST_API_KEY,
'X-Parse-Session-Token': PARSE_CREDENTIALS.PARSE_SESSION,
'Content-Type':'application/json'
}
}
}
}
}
}])
Many thanks
Regarding single error handler function which shows the ionicPopup:
You can emit an event using $rootScope.$emit from the error callback functions.
You can listen to all those events using $rootScope.$on in either run() method or in a factory and it will show the $ionicPopup
In your error handler:
$rootScope.$emit('errorEvent',
{"message" : "Pass your custom message here", "errorObject": errorObject}
);
In your event listener
app.run(function($ionicPopup) {
$rootScope.$on("errorEvent", function(event, data) {
// you can access that here
$ionicPopup.alert({
title: data.message
});
});
});
Update:
For the timeout option, in your getParams() function add timeout config property
function getParams() {
return {
timeout: 18000, // adjust this value
headers: {
'X-Parse-Application-Id': PARSE_CREDENTIALS.APP_ID,
'X-Parse-REST-API-Key': PARSE_CREDENTIALS.REST_API_KEY,
'X-Parse-Session-Token': PARSE_CREDENTIALS.PARSE_SESSION,
'Content-Type': 'application/json'
}
}
}
Let's say my current route is /books and I make an $http call to get all of the books we want to show a user. Normally, the call would resolve quickly and the books would be ng-repeated into the DOM. When we have an error, though (such as a timeout or there are no books returned), we update a common, global view that will overlay the content view and display a message like, "There are no books available." The common view is handled via a service with methods like CommonView.showLoading(), CommonView.showError("There are no books available."), and CommonView.hide(), etc.
Recently, I discovered that if the $http is not resolved quickly, the user may leave and go to another route (maybe /dinosaurs). Eventually, when the $http ends up resolving or being rejected, the promise call to display that common, global view will happen, resulting in an error view being displayed when there shouldn't be one, and the error will make no sense to the user (ie, user is at /dinosaurs and the error screen pops up with "There are no books available.").
I've seen that you can cancel an $http with a timeout promise, but this still seems like it could lead to race conditions (maybe you call cancel after processing of the resolve() or reject() has begun). I think it would be messy to have to check that the current route matches the route the $http was initiated from.
It seems like there should be some standard way to destroy $http calls on a route change or from a controller's $destroy method. I'd really like to avoid adding a lot of conditionals all over my gigantic app.
I can't find a great way to stop the processing of my callback if it's already started, but here's the $http wrapper I made to try and stop delayed callbacks from getting called after route changes. It doesn't replicate all of the $http methods, just the ones I needed. I haven't fully tested it, either. I've only verified that it will work in normal conditions (normal bandwidth with standard calls, ie httpWrapper.get(url).success(cb).error(err)). Your mileage may vary.
angular.module('httpWrapper', []).provider('httpWrapper', function() {
this.$get = ['$rootScope','$http','$q', function($rootScope, $http, $q) {
var $httpWrapper = function(config) {
var deferred = $q.defer();
var hasChangedRoute = false;
var canceler = $q.defer();
var http = null;
var evListener = null;
var promise = deferred.promise;
if ((config || {}).timeout && typeof config.timeout === 'Object') {
// timeout promise already exists
canceler.promise = config.timeout;
} else {
angular.extend(config || {}, {
timeout: canceler.promise
});
}
http = $http(config)
.success(function(data, status, headers, config) {
// only call back if we haven't changed routes
if (!hasChangedRoute) {
deferred.resolve({data:data, status:status, headers:headers, config:config});
}
})
.error(function(data, status, headers, config) {
// only call back if we haven't changed routes
if (!hasChangedRoute) {
deferred.reject({data:data, status:status, headers:headers, config:config});
}
});
evListener = $rootScope.$on('$locationChangeStart', function(scope, next, current) {
hasChangedRoute = true;
canceler.resolve('killing http');
evListener(); // should unregister listener
})
promise.success = function(fn) {
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
}
return promise;
};
angular.forEach(['get', 'delete', 'head', 'jsonp'], function(method) {
$httpWrapper[method] = function(url, config) {
return $httpWrapper(
angular.extend(config || {}, {
method: method,
url: url
})
);
};
});
angular.forEach(['post', 'put'], function(method) {
$httpWrapper[method] = function(url, data, config) {
return $httpWrapper(
angular.extend(config || {}, {
method: method,
url: url,
data: data
})
);
};
});
return $httpWrapper;
}];
});