First project in AngularJS and I started creating my services (factories) that I made modular like this
angular.module('app.services.public', [])
.factory('publicService', ['$http', function publicService($http) {
var results = {};
results.contact = function (name, email, message){
return $http.get();
};
return results;
}]);
That I then call in my main angular app by including it. When I call it, I need to listen for success or error
publicService.contact().success(callback).error(callback)
My question is, I'm going to be doing a lot of API requests through these services and seems to be bad code to listen to the error everytime since 90% of the time it will do the same thing.
How can I create a wrapper around the $http.get or around all factory calls?
So something like
apiCall = function (url, data, successCallback, errorCallback){
$http.get(url,data).success(function(){
successCallback()
}).error(function(){
if(errorCallback()){ errorCallback(); return; }
// or display general error message
})
}
I would recommend against converting promise-based into callback-based APIs. Angular adopted promises and it best to stay with them.
Also, stay away from $http-specific .success/.error and use promise .then/.catch APIs.
How wide do you need to cast your net to handle $http errors?
1) Say, it only applies to your publicService service, then you can "handle" it at the each function:
.factory("publicService", function($http, $q){
function handleError(){
// invokes error handlers
}
return {
onError: function(cb){
// register error handlers
},
doSomethingA: function(){
return $http.get("some/url/A")
.then(function(response){
return response.data;
})
.catch(function(error){
handleError(error);
return $q.reject(error); // still "rethrow" the error
}
},
doSomethingB: function(){
// similar to above
},
// etc...
};
})
Then you could separate request from error handling:
.controller("MainCtrl", function($scope, publicService){
publicService.onError(function(error){
$scope.showError = true; // or something like that
})
})
.controller("FunctionACtrl", function($scope, publicService){
publicService.doSomethingA()
.then(function(data){
$scope.data = data;
});
})
2) Of course, the above, would only apply to request made via publicService. If you want to catch all $http errors, you could implement an $http interceptors. I won't go into detail - there is enough info in documentation and elsewhere - but it would could work like below:
.factory("ErrorService", function(){
return {
onError: function(cb){
// register error handlers
},
broadcastError: function(error){
// invoke error handlers
}
};
})
Then in interceptor, use ErrorService as a dependency:
'responseError': function(rejection) {
ErrorService.broadcastError(rejection);
return $q.reject(rejection);
}
Then you could handle the errors globally:
.controller("MainCtrl", function($scope, ErrorService){
ErrorService.onError(function(error){
$scope.showError = true; // or something like that
})
})
You have the right idea. You can do it easily with a Factory.
myApp.factory(APIService, function(publicService, $http) {
return {
// create methods in here
...
contact: function(cb) {
$http.get(url,data).success(cb).error(function(err){
console.error('oh no!', err);
});
}
};
});
Then you can use it in your controllers.
APIService.contact(function(data){
console.log('response from the api!', data);
});
You can even move your error handler to its own factory as well.
I would suggest an implementation using angular's $q service.
angular.module('app.services.public', [])
.factory('publicService', ['$http', '$q', function publicService($http, $q) {
var results = {};
results.contact = function (name, email, message){
return $q.when($http.get());
};
return results;
}]);
Or rather than use the $q.when(...) method you can use $q.deferred like so:
angular.module('app.services.public', [])
.factory('publicService', ['$http', '$q', function publicService($http, $q) {
var deferred = $q.deferred();
var results = {};
results.contact = function (name, email, message){
$http.get().success(function(data){
deferred.resolve({
// assumes data retried from http request has a title and price attribute
title: data.title,
cost: data.price});
}).error(function(data){
deferred.reject(data);
});
};
return deferred.promise;
}]);
Related
I'm trying to create a db factory that returns data from the database to the client after the data is successfuly posted but it returns as 'undefined' for some reason.
My factory function looks like this:
uno.factory('adbFactory', ['$http', function($http){
var fact = {};
fact.get = function(http, query, isAll) {
//var query = "get all blog_posts";
http.post('php/adb/adb.php', {'query': query, 'all': isAll})
.success(function(data){
//console.log(data);
return data;
})
.error(function(){
console.log('Error...');
});
};
return fact;
}]);
And my controller resembles this:
uno.controller('newsCtrl', function($scope, $http, adbFactory){
$scope.derp = 'derp!!!!!';
console.log(adbFactory.get($http, 'get users 1', false));
});
don't worry about the 'get users 1 etc etc' string, i created a function in php that renders a SQL query based on given parameters. Is there something in my factory code i need to improve on??
I would advice returning the promise from the factory and handling the success and error events in the controller instead.
fact.get = function(http, query, isAll) {
return http.post('php/adb/adb.php', {'query': query, 'all': isAll});
};
uno.controller('newsCtrl', function($scope, $http, adbFactory){
adbFactory.get($http, 'get users 1', false).success(function(data) {
console.log(data);
});
});
fact.get method has no return statement, that's why it returns undefined.
Also, this callback is useless because it is called asynchronously
.success(function(data){
//console.log(data);
return data;
})
I think you want somethig like:
fact.get = function(http, query, isAll) {
return http.post('php/adb/adb.php', {'query': query, 'all': isAll});
};
uno.controller('newsCtrl', function($scope, $http, adbFactory){
adbFactory
.get($http, 'get users 1', false)
.success(function(data){
console.log(data);
})
.error(function(){
console.log('Error...');
});
});
You have to keep in mind that you are performing some asynchronous request.
You have two way to retrieve your data :
Following the callback way
Following the promise way
As you know, $http service return promise, and has some callback method, like .success() and .then() for example.
For promise, $q.defer() is a promise manager from the deferred API.
$q.defer() get 2 methods :
resolve(value) : which resolve our associated promise, by giving her the final value
reject(reason) : which resolve an promise error.
So you can do :
Service
(function(){
function Service($http, $q){
var defer = $q.defer();
//Callback way
function get(callback){
$http.get('app.php').success(function(data){
//Pass our data to the callback
callback(data);
});
}
//Promise ways
function getPromise(){
$http.get('app.php').success(function(data){
//Resolve the data
defer.resolve(data);
});
//Return our promise
return defer.promise;
}
return {
get: get,
getPromise: getPromise
};
}
angular
.module('app')
.factory('Service', Service);
})();
Controller
(function(){
function Controller($scope, Service) {
//Our callback method
function print(data){
console.log(data);
}
//Retrieve our data by using callback way
Service.get(print);
//Retrieve our data by using promise way
var promise = Service.getPromise();
//When promise is resolved
promise.then(function(data){
//Retrieve our data
console.log(data);
});
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
But what should i use ? I think that use promise is better than callback, because you can handle easily your request. Moreover, you can perform promise chaining, and avoid the famous callback hell.
My code makes many AngularJS $http requests. Often it is 3-4 at the same time.
Is there some way that I can intercept the http messages so that I get just one alert pop up if the internet connectivity is lost and there are multiple requests going on? I have seen other sites that do this but then if the action requires a number of http calls it seems that I could get more than one error popup coming.
If possible I would like to do this in just the one place in my code.
You need add responseInterceptors inside you $httpProvider in configuration phase of angular. This interceptor gets called after angular $httpProvider processing the response.
CODE
module.config(['$httpProvider', function($httpProvider) {
var interceptor = ['$rootScope', '$q', '$location', function(scope, $q, $location) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 500) {
alert("Internal Server Error")
return;
}
if (status == 404) {
alert("Page not found")
return;
}
// otherwise
return $q.reject(responseInterceptors);
}
return function(promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
}]);
Above code will provide you better control on error handling when any request fails.
For more details refer this anwser.
Update
For showing alert only once we could create a service,if error occurred then that will handle the set error variable
Service
module.service('errorService',function(){
//getter
this.getErrorFlag = function(){
return this.isError;
}
//setter
this.setErrorFlag = function(val){
this.isError = val;
}
});
Interceptor
module.config(['$httpProvider', function($httpProvider) {
var interceptor = ['$rootScope', '$q', '$location','errorService', function(scope, $q, $location,errorService) {
function success(response) {
return response;
}
function error(response) {
//setting error variable
errorService.setErrorFlag(true)
return $q.reject(responseInterceptors);
}
return function(promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
}]);
Inside you controller put all $http call promises inside $q.all, and when promises gets resolved then check for isError flag of errorService.
IsError flag will be true if error occurred at least one & by using it you can show error only once.
Controller
module.controller('appCtrl',['$scope','$q','errorService',function($scope,$q,errorService){
var ajax1 = $http.get('url').then(function(){
},
function(){
});
var ajax2 = $http.get('url').then(function(){
},
function(){
});
var ajax3 = $http.get('url').then(function(){
},
function(){
});
errorService.setErrorFlag(false);
$q.all(ajax1, ajax2, ajax3).then(function(data){
//check for isError flag from service
if(errorService.getErrorFlag())
alert('Error occurred while processing request.'); //this will show only once
else
alert('No error occurred while processing request.')
});
}]);
Hope this could help you.
Thanks.
If you don't want to use an interceptor, you could simply process the error callback of your $http calls:
$scope.httpError = null;
$scope.processHttpError = function() {
// If you don't already have got an http error
if (!$scope.httpError) {
$scope.httpError = "Cannot load stuff";
}
};
$http.get('/someUrl')
.success(function(data, status, headers, config) { ... })
.error($scope.processHttpError);
New to angular here. I am trying to data then pass it into a factory for use. I am trying to get the events to fire in a certain order with using the $http within angular. I was pointed in the direction of using .run to do the first population, pass it into the factory, which then passes it into the controllers. The factory has a function which then would allow me to "refresh" the data being shared with the controllers.
I received some great help setting up the refresh function, but I am struggling get the data to populate initially. Here is my attempt:
.run("getDataForLevels", function($http){
var getIt = $http.get("/assets/images/generated.json")
.success(function(data){
return data;
});
return getIt;
})
.factory("UserService", function($http, getDataForLevels) {
dataFactory = getDataForLevels;
dataFactory.currentData = null;
dataFactory.update = function () {
return $http.get("/assets/images/generated.json")
.success(function(data){
dataFactory.currentData = data;
return data;
});
};
return dataFactory;
});
I then Add the dependency of UserService to the controllers and pass in the data to the controllers. I seem to be betting an error [ng:areq] on this. Any/all help would be much appreciated. Thanks!!
you can't do like this :
.run("getDataForLevels", function($http){
var getIt = $http.get("/assets/images/generated.json")
.success(function(data){
return data;
});
return getIt;
})
it only take one argument, as a function.
you can do like this:
.run(function($http,$rootScope){
$http.get("/assets/images/generated.json")
.success(function(data){
$rotScope.data=data;
});
})
.factory("UserService", function($http, $rootScope) {
dataFactory.update = function () {
return $http.get("/assets/images/generated.json")
};
return dataFactory;
});
in controller inject dependencies and do like this:
var dataFactory = $rootScope.data;
dataFactory.currentData = null;
UserService.update ()
.success(function(data, status, headers) {
console.log(data);
})
.error(function(data, status, headers, config) {
console.error('error in loading File list');
});
Here's my code in the service.
this.loginUser = function(checkUser) {
Parse.User.logIn(checkUser.username, checkUser.password, {
success: function(user) {
$rootScope.$apply(function (){
$rootScope.currentUser = user;
});
}
});
};
Here's my code in the controller:
$scope.logIn = function(){
authenticationService.loginUser($scope.checkUser);
console.log($scope.currentUser)
};
So, what I want to do is, execute some code AFTER the completion of AJAX call, whose success function sets the value of $scope.currentUser, which, I can use for some conditional logic (like redirecting etc)
The success function is correctly setting the value, but the console.log should be executed AFTER the execution of authenticationService.loginUser() function.
You need to return a promise using $q and act on that.
For instance in your service:
this.loginUser = function(checkUser) {
var deferred = $q.defer();
Parse.User.logIn(checkUser.username, checkUser.password, {
success: function(user) {
$rootScope.$apply(function (){
$rootScope.currentUser = user;
});
deferred.resolve();
}
});
return deferred.promise;
};
Then in your controller act on the success:
$scope.logIn = function(){
authenticationService.loginUser($scope.checkUser).then(function() {
console.log($rootScope.currentUser));
});
};
Try using $rootScope.$broadcast in your service then listen for it in your controller:
Service
Parse.User.logIn(checkUser.username, checkUser.password, {
success: function(user) {
$rootScope.$apply(function (){
$rootScope.currentUser = user;
$rootScope.$broadcast('user.online');
});
}
});
Controller
$scope.$on('user.online',function(){
[ DO STUFF HERE ]
});
This isn't the best way to do this though #comradburk's use of $q is probably a better way.
If your application wait for external result, you should use $q for return a promise. If you are using angular-route or ui-router components, you can use resolve param for this. Take a look ngRoute documentation. In there has a example based in resolve param.
https://docs.angularjs.org/api/ngRoute/service/$route
i think you have two options here
as answered by comradburk, use promises:
in Services:
this.loginUser = function(checkUser) {
var deferred = $q.defer();
Parse.User.logIn(checkUser.username, checkUser.password, {
success: function(user) {
deferred.resolve(user);
}
});
return deferred.promise;
};
in controller:
$scope.logIn = function(){
authenticationService.loginUser($scope.checkUser).then(function(user) {
$scope.currentUser = user;
});
};
using resolve, resolve your service at route level (...or state level in case you are using ui-router) before controller initialization and insert it as a dependency - helpful in scenarios like user authentication where you dont want user to be able to navigate further if authentication fails. from docs
https://docs.angularjs.org/api/ngRoute/service/$route
YOMS (Yet One More Solution):
this.loginUser = function(checkUser, onSuccess) {
Parse.User.logIn(checkUser.username, checkUser.password, {
success: function(user) {
$rootScope.$apply(function() {
$rootScope.currentUser = user;
if (typeof onSuccess == 'function') onSuccess(user); // optionally pass data back
});
}
});
};
$scope.logIn = function(user, function(returnedUser) {
// console.log(returnedUser); // Optional, The returned user
console.log($scope.currentUser)
}) {
authenticationService.loginUser($scope.checkUser);
};
I'am not pro in Angular and am still lerning. Hope I get some help here.
I want to build an App with different views. I need to detect the browser and also fetch some data from a server. For this I created a service, where I do this work.
My desire is to use the data of the service all views. How is proper way to store and cache the data so that I can use it in all my Views/Controllers?
Here is what I got so far.
My Service:
.factory('DataService', function($http, $q, $timeout) {
var data = { };
return {
notes: function() {
// This exposed private data
return data;
},
addItem: function(itemname, itemvalue) {
// This is a public function that modifies private data
data[itemname] = itemvalue;
}
getPlatform: function() {
var getPlatformData = function() {
var deferred = $q.defer();
BrowserDetect.init();
deferred.resolve(BrowserDetect.OS);
return deferred.promise;
};
return {
getPlatformData: getPlatformData
};
},
getServerData: function() {
//if(!data.getServerData){
var getData = function() {
var deferred = $q.defer();
$http({
url: 'js/fakeGet.json',
method: 'get',
dataType: 'json',
}).success(function(data) {
data.scanResponse = data;
deferred.resolve(data);
})
return deferred.promise;
};
return {
getData: getData
};
//}
// return data.scanResponse;
}
};
});
My controller:
DataService.getPlatform().getPlatformData().then(function(platform) {
console.log('Another browserDetect request');
$scope.platform = platform;
DataService.addItem("platform", $scope.userPlatform);
});
First of all, as nordyke mentioned in his answer, you'd better split the service to smaller ones.
Second, you're asking for how to caching the data, and since you're using promise, $q.when() is what you need. I will take the getPlatform as an example to get you started:
.factory('DataService', function($http, $q, $timeout) {
var os; // this variable is used to store the result
return {
getPlatform: function() {
var getPlatformData = function() {
if (!os) { // no previous data available, look into other service to fetch the data
var deferred = $q.defer();
BrowserDetect.init();
os = BrowserDetect.OS; // store data
deferred.resolve(os);
return deferred.promise;
}
return $q.when(os); // there is previous data, return it as promise
};
return {
getPlatformData: getPlatformData
};
}
};
});
In this way, OS information is cached, and
DataService.getPlatform().getPlatformData().then(function(platform) {
...
});
will only fetch the platform information once during the life-time of the DataService. You can apply the same idea to getServerData as well to cache the data from the server.
Caching your data in a service singleton is a good approach, and I like your straightforward implementation of it. My only recommendation would be to split up your 3 concerns into separate services.
Browser Detection
Server Requests (which will be split up even more once you have more requests.)
Data Caching