I'm aware that Angular can handle promises from within controllers. For example:
function MyCtrl($scope) {
$scope.myvar = getDeferredPromise();
}
The main angular digest loop handles this gracefully, assigning whatever value the deferred function finally returns later to myvar.
However, although the $http.get() method returns a promise, I cannot get it to work in this way. For example:
function MyCtrl($scope, $http) {
$scope.myvar = $http.get('/url');
}
The 'promise' the get method returns has a success method which takes a function that is assigned the data that one would wish to assign to myvar.
However, it also has a then method - but that is given the entire response object - not just that data part! This is what seems to end up getting assigned to myvar!
This fiddle may help: http://jsfiddle.net/QKnNC/1/
Am I doing something wrong here? Or is this somehow 'by design'?
ng.$http
The $http service is a function which takes a single argument — a
configuration object — that is used to generate an HTTP request and
returns a promise with two $http specific methods: success and error.
$http returns a promise, so you need to chain then to get the data.
IPService.getV1().then(function (response) {
console.log(response)
$scope.value1 = response.data;
});
then is the general promise function, that takes a success and error callback and you get the resolved value, whatever it may be. success and error are $http specific, and are aliases for then with one exception: they set a bunch of useful arguments rather than just the data. See the source.
It is by design. Your getV2() method is what you want. Since you are using GET, you could save the result of your promise and return that on subsequent calls to getV2():
var v2promise, v2data;
return {
getV2: function() {
if(!v2promise) {
v2promise = $http.get('http://ip.jsontest.com/').then(
function(response) {
v2data = response.data;
return v2data;
});
}
return v2promise;
}
}
Related
I have a service that (when it's all said and done) updates a value on the database.
I would like to update the view scope based on the result (success/fail) but of course the http request used by the service is asynchronous so the return value is not immediately available and comes up undefined in the controller.
If I were making the http request inside the controller, I would update the scope inside the callback function, but because Im using a service, the scope's umm... scope(?) is not available to it.
Im thinking a Promise is what I need to be returning but perhaps there is something more simple.
SERVICE
.service('doStuff',function($http){
this.update = function(data) {
$http.post('http://api.internet', data).then(function(res){
return(res.data.result);
});
}
})
CONTROLLLER
/* service is injected into controller etc. */
var result = doStuff.update(data);
p(result); // undefined (as expected)
I figured since Im returning from the http callback, it would wait for the result to be available before returning but I guess Im missing something.
Since $http is always async, you cannot return anything in the call back function. It is as good as not returning anything.
What you need to do is you need to return the $http promise, and then handle the callback functions in your controller.
Service:
.service('doStuff', function($http) {
this.update = function(data) {
return $http.post('http://api.internet', data);
}
})
Controller:
doStuff.update(data).then(function(result){
p(result);
});
Foremost, you need to return the query itself. Looks like
this.update = function(data) {
return $http.post('http://api.internet', data).then(function(res){
return(res.data.result);
});
}
Next step, you need get out of the promise function.
doStuff.update(data)
.then(function(res) {
//someone if request is success
})
.catch(function(rej) {
//someone if request is reject
});
I'm creating a factory to take a userId from one page, make a call to a REST API, and return the results on the following view. My initial attempts were largely taken from this answer but - unsurprisingly - I keep getting caught in a situation where the doesn't respond in time and the get() method returns an empty array.
Here's the factory itself
app.factory('GetMessages', function() {
var messages = []
function set(userId) {
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages = docs
})
}
function get() {
return messages;
}
return {
set: set,
get: get
}
});
For what it's worth I'm having no trouble getting the userId into the factory as it's just passed in on a function like this
view:
<a ng-click='passToFactory(message.user.id)' href='/home/inbox/reply'>Reply</a>
controller:
$scope.passToFactory = function(id) {
GetMessages.set(id);
};
and the controller for the following view is just
$scope.messages = GetMessages.get()
The issue I'm having is that after the factory returns the empty set no further changes from the factory are recognized (even though after time elapses it does get the proper response from the API) and $scope.messages remains empty.
I've attempted to move the API call to the get method (this hasn't worked as the get method often does not get the userId in time) and I can't find a way to use a promise to force get() to wait on set() completing.
I'd prefer to keep using Restangular in the eventual solution but this is a small thing that has taken too much time so any fix works.
I'm fairly new to Angular so I'm sure there's something totally obvious but right now I'm just lost. Thanks.
The race condition that you have is that the function inside the .then method is executed asynchronously after the call to the set function. If the get function executes before the $q service fulfills the promise, the get function returns an empty array.
The solution is to save the promise and chain from the promise.
app.factory('GetMessages', function() {
var promise;
function set(userId) {
promise = Restangular.all('/api/messages/').getList({'_id': userId});
}
function get() {
return promise;
}
return {
set: set,
get: get
}
});
In your controller, chain from the promise.
GetMessages.get.then( function (docs) {
$scope.messages = docs;
}) .catch ( function (error) {
//log error
};
For more information on chaining promises, see the AngularJS $q Service API Reference -- chaining promises.
You are breaking the reference to the original messages array when you reassign it.
Try:
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages.concat(docs) ; // keep same array reference
});
Simple example to explain why it isn't working
var arr = [];
var x = arr;
arr = [1,2,3]; // is now a different array reference
console.log(x); // is still empty array. x !== arr now
cherlietfl is right.
The problem is that you break the reference to the messages array since you assign a new array to messages inside your get function. But concat is doing this as well.
Try this:
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages.splice(0, messages.length); // clear the array
messages.push.apply(messages, docs); //add the new content
});
Try assigning you function to the scope. Then call that function in the model. Like so:
// controller
$scope.getMessages = GetMessages.get;
View:
<div ng-repeat="message in getMessages()"></div>
This way when the request call finishes and the digest cycle goes through the watchers again, the get function will be called and you will get your messages.
So, I've got an Angular app that makes restful calls to the server. There is a service that wraps up the calls to the server. I currently have a method on the service that simply returns the promise from the $http service. I'd like to add some additional processing on that method call, but I'm not sure how to do it because of the asynchronous nature of the promise.
Currently in typescript:
class BoardService {
private $http;
constructor($rootScope: IRootScope, $http: ng.IHttpService) {
this.$http = $http;
}
fetchBoard(id: number) {
return this.$http.get("/api/board/" + id);
}
}
I'd like to get it to something like this:
fetchBoard2(id: number) {
this.$http.get("/api/board/" + id).success(function(data)
{
// Manipulate the data
});
// return manipulated data;
}
How would you do this?
Tricky sentence warning! Because promises are asynchronous, anything returning data based on data from a promise must itself return a promise. You want fetchBoard2 to return a promise that gets resolved once the $http promise has come back and you've manipulated the data. You do this with Angular's $q service.
fetchBoard2(id: number) {
var deferred = $q.defer();
$http.get("/api/board/" + id).success(function(data) {
var newData = doSomething(data);
deferred.resolve(newData);
});
return deferred.promise;
}
Managing extra deferred objects gets quickly fiddly, so you can use then to insert your own manipulation into the pipeline.
fetchBoard3(id: number) {
return $http.get(...).then(function(data) {
return doSomething(data);
});
}
For more detail, here's a good article.
The $http module only exposes the asynchronous version of XMLHttpRequest so the signature you're looking for is impossible. Unless want to fallback to another framework (e.g. jQuery), you'll have to use the Promise object returned.
Think of it as a factory object with which you register handlers that will be called when the data comes back. They can be chained, so if you want to process the data before handing it downstream, you can simply do that in the handler you register with the then method. Any result you return in the handler will become the data to the next then handler.
(Please note that unlike success(), the argument to your handler is the IHttpPromiseCallbackArg type not the data itself.)
I return a resource with a URL
$resource("http://foo.com/bar.json").get().
$promise.then(function(data){ $scope.result = data},
function(error){ $scope.msg = "error" } );
Resource returns
["item1"...."item_n",.....,"$promise", "$resolved", "$get", "$save", "$query", "$remove", "$delete"]
Why do I get all those objects in my data set. I'm guessing $promise just returns all this and waits for the server response. But once I have the server response where can I just get my server data without the Promise jargon?
If you look at the angular source here:
https://github.com/angular/angular.js/blob/master/src/ngResource/resource.js#L505
There is a toJSON method on the Resource prototype chain that will accomplish this for you.
For example:
$resource("http://foo.com/bar.json").get(function(res) {
$scope.result = res.toJSON();
});
You need to return wrapped result like {'result': { 'some_key': 'some_val' }} from your backend.
Or just do like described above.
Diary.getSharedWithMe(function(data) {
delete data.$promise;
delete data.$resolved;
_self.sharedDiariesWithMe = data;
}, function(error) {
console.log(error)
});
$resource returns an object or array that will have your data when the call completes. All those functions are there to help you out and $resource is mainly intended for CRUD operations. If you want the data, you have to wait for it to get returned so you might as well use the promise. If you want to strip all of those properties you can use angular.toJson to convert it to json, but angular does that for you when posting it back to a resource or $http call so you shouldn't have to.
$scope.data = $resource("http://foo.com/bar.json").get();
// $scope.data does not have your data yet, it will be
// populated with your data when the AJAX call completes
...
// later in a call from a save button maybe you can just do
// this to post your changes back:
$scope.data.$save();
So in case someone else is stumbling here and didn't understand promises/angularjs here is what is going on. When you use .then() or .get() you get a promise and some helper functions all in the same object. This is awesome because then you don't worry about callbacks being defined and whether data is available because the promise object always has some properties. This object contains your raw data in another object within. So the promise object is nested, you just have to reference the data object within when the data is ready.
Here's what I was doing
$resource("http://foo.com/bar.json").get().
$promise.then(function(data){ $scope.result = data},
//data is actually a promise object.
function(error){ $scope.msg = "error" } );
promise object
Note the data is actually under another object called "data". So in your success callback to get just the data you should do in this case: data.data
To automatically remove them from every request, you can add an interceptor:
angular.module('app').config(config);
config.$inject = ['$httpProvider'];
function config($httpProvider) {
$httpProvider.interceptors.push(interceptor);
}
interceptor.$inject = [];
function interceptor() {
return {
request: (config) => {
if (config.data) {
delete config.data.$promise;
delete config.data.$resolved;
}
return config;
}
};
}
what's the best way to declare a service I found this 2 different ways i can't seem to see the difference:
first method:
angular.module('app', [])
.factory('Data', ['$http',function($http){
return {
get: function(fileName,callback){
$http.get(fileName).
success(function(data, status) {
callback(data);
});
}
};
}]);
second method:
angular.module('app', [])
.factory('Data', ['$http', function($http){
var Url = "data.json";
var Data = $http.get(Url).then(function(response){
return response.data;
});
return Data;
}]);
Which one is better and why?
Thanks in advance.
There are a few things to separate out here.
Object vs Promise
Services are singletons, so in the second method (returning just the promise) your data will never be updated again. That's often the desired result. In your first method, it will be called fresh every time (though $http has a cache option).
From my perspective, one would only return a service object if there were multiple methods (e.g. get, create, delete, etc.) or it needed to be called multiple times. Otherwise, we're just piling on cruft.
Promise vs Callback
Promises are awesome - we should be leveraging them. Passing in a callback is nice, but it's also very limiting. With promises, we can easily chain them together, e.g.:
Data.get()
.then( massageDataFn )
.then( secondMassageFn )
.then(function ( data ) {
$scope.items = data;
});
Besides, $http already returns a promise. Why throw that away?
Method Parameters
Your former (object method) took in some parameters. While I am wary of a controller passing in an URL, there are some cases where it's desirable. But you can do this by just returning a function rather than an object:
.factory( 'Data', [ '$http', function ( $http ) {
return function ( fileName, callback ) {
// $http call here...
};
}]);
And then only the consideration is Object vs Promise, discussed above.
Conclusion
If your service has an API, return an object with public API methods, where each of those methods returns a promise. If your service is just to get some data once, just return the promise and be done with it.
The latter requires foreknowledge of the url and the callback function. The latter allows you to set both the target url and the callback, which is far more flexible. I would suggest going with the former in most cases, but it really depends on what you are trying to do.
The first sample makes the asynchrony of the HTTP request explicit for the service user, which is preferable, I think, to handing the service user a promise object (which is what "Data" is in the second sample) and letting the user proceed with its imperative operations, perhaps depending on a "Data" object that hasn't been filled in yet.
Your second method is a little more verbose, but one major benefit is the ability to create private properties / methods, and only return the public object.
Note that this should be planned around with regards to testing etc - from personal experience with Angular, private methods and properties are more difficult to unit test etc.
As an example:
angular.module('app', [])
.factory('Data', ['$http', function($http){
var private = {
property: 100,
method: function(data) {
return data * 2;
}
};
var public = {
property: true,
property_two: false,
method: function(data) {
return private.method(data + private.property);
}
};
return public;
}]);