I'm implementing just a thin wrapper around $http for our REST API, and I'm wanting it to return a promise in the same way as $http does (after I massage the data).
Here's my service:
Services.service('Item', ['$http', '$q', function($http, $q){
var deferred = $q.defer();
var getSuccess = function(data, status, headers, config){
var item = angular.copy(data);
item.primaryImage = 'https://my.cdn.com/' + item.meta.images[0].s3id;
if(item.meta.source_link !== null) {
item.sourceLink = item.meta.source_link.url;
}
deferred.resolve(item, data, status, headers, config);
};
var getError = function(data, status, headers, config) {
deferred.reject(data, status, headers, config);
};
this.get = function(userID, itemID) {
$http({
method: 'GET',
url: '/api/items/' + userID + '/' + itemID
}).success(getSuccess).error(getError);
return deferred.promise;
};
}]);
But from my understanding of the documentation, I have to use .then(success, error, always) rather than .success().error().always() like I can with $http.
Is it possible to implement promises in the same way that $http does? I would love to do this
var req = Item.get($routeParams.userID, $routeParams.itemID);
req.success(function(item){
window.console.log('Got an item!', item);
});
.error(function(item){
window.console.log('Damn. It failed.')
})
This question made me search for a bit after understanding what you actually wanted.
I made a plnkr showing how I solved this: http://plnkr.co/edit/LoCuwk26MEZXsugL1Ki5
Now, the important part is:
var promise = defered.promise;
promise.success = function(fn) {
promise.then(function(res) {
fn(res);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, function(err) {
fn(err);
});
return promise;
};
return promise
This code comes straight from the angular source code. So I believe it is the way to go if you want to replicate that style.
I did notice an error in your code though. All services are singletons. Which means you only create one deferred object. You should create one on each call and use that.
Related
To resolve my issue I have gone through many articles on different sites, but none resolved it.
I'm writing a simple AngularJS application. I'm quite new to Angular. I have written a factory method which call the $http service which gets the data from the web api. Web api is running fine and its returning the JSON object as expected.
Angular Code
var app = angular.module("app", [])
.controller("controller", function ($scope, WebFactory) {
$scope.data = "data";
$scope.error = "error";
$scope.data=WebFactory.getData();
})
.factory("WebFactory", function ($http) {
var obj = {};
obj.getData = function()
{
$http({
method: "GET",
url: "http://localhost:37103/api/employee",
}).then(function success(response) {
return response.data;
})
.then(function error(response) {
return response;
});
return 'No data';
}
return obj;
});
HTML code
<body ng-controller="controller">
data: {{data}}
<br/>
error: {{error}}
<br />
I have spent 2 days, but still dont know why its not working.
Try something like this instead:
var app = angular.module("app", [])
.controller("controller", function ($scope, WebFactory) {
$scope.data = "data";
$scope.error = "error";
$scope.data = {}
WebFactory.getData().then(function success(response) {
$scope.data = response.data;
});
})
.factory("WebFactory", function ($http) {
var obj = {};
obj.getData = function()
{
return $http({
method: "GET",
url: "http://localhost:37103/api/employee",
})
}
return obj;
});
First of all, you're missing an ng-app declaration
Secondly, you are misusing the then callback. It accepts three parameters: success callback, error callback, finally callback.
As the first then executes the success, then it executes the second callback that always returns the response, but I assume it is not intended to be that way and you could use the get function which is more easy to use.
It should be:
$http.get("http://localhost:37103/api/employee").then(function(response){
//Do something with successfull response
},
function(error){ //do something with the error});
See documentation about promises
Lastly, you are dealing with promises and async code, yet returning response or string instead of the promise with the result. So the getData() should look like this:
obj.getData = function(){
return $http.get("http://localhost:37103/api/employee");
}
And use the then(success,error,finally) callbacks in the controller or if you want to provide values on the error/finally callbacks in the service, you should use the $q service
obj.getData(){
var deferred = $q.defer();
$http.get(...).then(function(response){
deferred.resolve(response.data);
},
function(error){
deferred.reject("No Data");
});
return deferred.promise;
}
Of course you would have to provide $q service to the WebFactory
I have a factory returning an object calling an internal resource containing JSON as so:
.factory('cardFactory', function ($q, $http) {
return {
getOtherStuff: function () {
var deferred = $q.defer(),
httpPromise = $http.get('/static/cards.json');
httpPromise.then(function (response) {
deferred.resolve(response);
}, function (error) {
console.error(error);
});
return deferred.promise;
}
};
});
In my controller I call it like this:
var cardSuccess = function(data, status, headers, config) {
$scope.cards = data.data;
};
cardFactory.getOtherStuff()
.then(cardSuccess, cardError);
In the browser $scope.cards in populated but on the device it doesn't populate.
Any ideas why?
Hmm.., not sure.
I have it in a different way in my Ionic app, working great.
.factory('cardFactory', function ( $http ) {
var promise;
var cards = {
getOtherStuff: function() {
if ( !promise ) {
// $http returns a promise, which has a then function, which also returns a promise
promise = $http.get( '/static/cards.json' ).then(function (response) {
// The then function here is an opportunity to modify the response
// The return value gets picked up by the then in the controller.
return response.data;
});
}
return promise; // Return the promise to the controller
}
};
return cards;
})
Then, on the controller, calling it by:
$scope.getData = function() {
// Call the async method and then do stuff with what is returned inside our own then function
cardFactory.getOtherStuff().then(function(d) {
$scope.cards= d;
});
}
$scope.getData();
Hope it helps.
[EDIT:] Could it be the $http.get url, being relative? Have you tried with an absolute url?
Replace $scope.cards=data.data with "$scope.cards=data"
var cardSuccess = function(data, status, headers, config) {
$scope.cards = data;
};
try removing the leading slash in your url for $http. Try using 'static/cards.json' instead of '/static/cards.json'
I actually had the same problem and this fixed it for me. Hope it helps!
Dear AngularJS savies,
How to set $scope.data in the controller having a promise returned from a factory. All I can do is work with the data returned in the scope of the method of the factory's object and I cannot "load" the data in a value existing in the controller's scope. How can I pass the data into controller's scope?
The code sequences as following
This is the factory:
var features = {};
// the method returns a promise tot he controller needed to be delt with .then()
features.getPromise = function(){
//make a promise
var promise = $http({method: 'GET', url: '../../LIBS/inc/getAllGeoFeatures.php'})
.success(function(data, status, headers, config){
return data;
})
.error(function(data, status, headers, config){
$log.warn(data, status, headers, config);
});
return promise;
};
This is the controller:
$scope.data = 'null';
factGetFeat.getPromise().then(function(promise){
$scope.data = promise;
$log.info($scope.data); //gets the object all right
// here I can work with the data but I cannot share it with the controller
// i.e. with the view
});
$log.info($scope.data); //$scope.data is still null and I need it loaded with the data
I need some counceling because I sense I go nowhere
I even tried to load $scope.data with the outcome of the method, but I only get the promsise object and not the data:
Object {then: function, catch: function, finally: function}
Please advise.
P.S. I use angular 1.2.x
Thank you very much for your time and patience.
It should be
factGetFeat.getPromise().then(function(promise){
$scope.data = promise;
$log.info($scope.data); //gets the object all right
}).then(function(){
// here you can see $scope.data updated with a new value
$log.info($scope.data);
});
as alternative:
var promise = factGetFeat.getPromise().then(function(promise){
$scope.data = promise;
$log.info($scope.data); //gets the object all right
});
promise.then(function(){
// here you can see $scope.data updated with a new value
$log.info($scope.data);
});
.then is evaluated when 1st promise resolved
You are doing it almost right. You only need to return the promise from your service.
If I understand you question you are concerned about $scope.data being null when you call $log.info($scope.data) on the very last page. And that is perfectly correct. $scope.data will be set later, when the HTTP call is finished successfully.
If you need to do any operations only when the data arrived, you must include it success callback.
Here is how it could look like:
var app = angular.module('app',[]);
app.factory('featuresService', ['$http', '$log', function($http, $log) {
var getPromise = function() {
//create the promise, this will be returned from this function
var promise = $http({method: 'GET', url: '../../LIBS/inc/getAllGeoFeatures.php'});
//on error do some logging here
promise.error(function(data, status, headers, config) {
$log.warn(data, status, headers, config);
});
return promise;
};
return {
getPromise: getPromise;
}
}]);
app.controller('testCtrl', 'featuresService', function($scope, featuresService) {
//set initial values
$scope.data = null;
$scope.vectorAllLibs = null;
var init = function() {
featuresService.getPromise().then(function(data) {
$scope.data = data;
$scope.vectorAllLibs = convertVectorAllLibs(data);
};
};
var convertVectorAllLibs = function(data) {
return ol.source.GeoJSON({ projection: 'EPSG:3857', object: data });
};
$scope.processVectorAllLibs = function() {
if (!$scope.vectorAllLibs) {
alert('sorry, no data yet!');
return;
}
//process here
};
init();
});
Hi all I am new to angular js,here I want to fetch the json from server to the controller so that I can handle the data,but whats happening here is the first alert -11 is called then alert 2 is called and in the end alert 1 is being called ,I read about promise so I tried it but still not working,I want it to happen one after another please help me .Thanks in advance
sampleApp.controller('firstpage', function ($scope, $q, $http, $templateCache, onlinedata, offlinedata) {
$scope.message = 'This is Add new order screen';
var defer = $q.defer();
defer.promise.then(function () {
$scope.method = 'POST';
$scope.url = 'http://ncrts.com/ncrtssales_compadmin/webservices/user_login';
$scope.fetch = function () {
$scope.code = null;
$scope.response = null;
alert($scope.response + 11) //alert11
$http({
method: $scope.method,
url: $scope.url,
cache: $templateCache
}).
success(function (data, status) {
$scope.status = status;
$scope.message = data;
alert($scope.message + 1) //alert1
}).
error(function (data, status) {
$scope.data = data || "Request failed";
$scope.status = status;
});
};
$scope.fetch();
})
.then(function () {
alert($scope.message + 2); //alert 2
})
defer.resolve();
//alert($scope.remember)
});
You have to use the resolve and reject functions to handle the response with the promise. You can find more information about promise in the AngularJS documentation. I made a little change in your code to show you how you can achieve what you want.
sampleApp.controller('firstpage', function ($scope, $q, $http, $templateCache, onlinedata, offlinedata) {
$scope.message = 'This is Add new order screen';
var defer = $q.defer();
$scope.method = 'POST';
$scope.url = 'http://ncrts.com/ncrtssales_compadmin/webservices/user_login';
$scope.fetch = function () {
$scope.code = null;
$scope.response = null;
alert($scope.response + 11) //alert11
var defer = $q.defer();
$http({
method: $scope.method,
url: $scope.url,
cache: $templateCache
}).
success(function (data, status) {
//$scope.status = status;
$scope.message = data;
alert($scope.message + 1) //alert1
defer.resolve({
status: status,
message: data
});
}).
error(function (data, status) {
var data = data || "Request failed";
//$scope.status = status;
defer.reject({
status: status,
message: data
});
});
return defer.promise;
};
$scope.fetch().then(function (data) {
alert('Status: ' + data.status);
alert('Message: ' + data.message);
});
});
I am very confused by the formatting of your code. It may help you to write the asynchronous call in a service and then inject the service into your controller. Keeping that in mind, here is some general advice that I've learned regarding asynchronous calls and promises in AngularJS:
Your service should initially return a deferred promise. This promise
should then be resolved on success() of the asynchronous call.
After $http() returns, but before success() returns, you have an
unresolved promise. If you have code that you want to run after the
promise is resolved, you need to use then() to indicate that you
want to execute the code within the then() block once you've
received data (and resolved the promise).
Not using then() for this
situation will cause errors because you will be attempting to access
data that doesn't yet exist.
It seems from your code that you are aware of the necessary coding strategy for what you need, but it will always help to isolate asynchronous calls into services so that the framework can make your life easier. Good luck.
I'm trying to bind some data being returned from an API to my scope using promises with $q, I am able to pull the data from the server without any issue (I can see JSON being returned using fiddler) however the $scope variable remains empty, any help would be greatly appreciated! Thanks in advance.
Code:
toDoListService.js
app.factory("toDoListService", function ($http, $q) {
var deferred = $q.defer();
return {
get: function () {
$http({ method: 'GET', url: '/api/todo/' }).
success(function (data) {
deferred.resolve(data);
}).
error(function (data, status, headers, config) {
deferred.reject(status);
});
return deferred.promise;
}
});
toDoListController.js
app.controller("toDoListController", function($scope, toDoListService){
$scope.toDoList = toDoListService.get();
});
First of all you should put var deferred = $q.defer(); in your get function, so that every get has it's own deferred object.
Second what get actually returns is a promise. So you need to access you data in this way:
app.controller("toDoListController", function($scope, toDoListService){
toDoListService.get().then(function(data){
$scope.toDoList = data;
});
});
Right now, your $scope.toDoList is bound to a promise. This means of binding used to work, but was deprecated in, I think, 1.2.
As Michael suggests, you must do:
app.controller("toDoListController", function($scope, toDoListService){
toDoListService.get().then(function(data){
$scope.toDoList = data;
});
});
Also, using $q is not required here at all, as $http returns a promise anyway. Therefore, you could just do:
app.factory("toDoListService", function ($http){
return {
get: function () {
return $http({ method: 'GET', url: '/api/todo/' });
}
};
});
You can simplify your code by using this:
toDoListService.js
app.factory("toDoListService", function ($http, $q) {
return {
get: function () {
return $http({ method: 'GET', url: '/api/todo/' });
}
}
});
toDoListController.js
app.controller("toDoListController", function($scope, toDoListService) {
toDoListService.get().then(function(response){
$scope.toDoList = response.data;
return response;
});
});
Be sure to return response in your success callback, otherwise chained promises would not receive it.