I've tried to strip out the details and make this fairly generalized...
Using 1.2 rc2 my code worked fine, after updating to 1.2 stable and correcting for $parse changes I've run into a binding problem. Before the update, the following code worked without any issues. updateChildObject() gets called from the html page.
.when('/the-page/', {
controller: function($scope, serviceResults, FactoryService) {
$scope.object.childObject = serviceResults;
// this function used to work. Now assigns the function to the
// scope rather than the results
$scope.updateChildObject = function(args) {
$scope.object.childObject = FactoryService.getSomethingFromServer(args);
};
},
resolve: {
serviceResults: function(FactoryService) {
return FactoryService.getSomethingFromServer(args);
}
}
Since this is failing now ($scope.object.childObject appears to get set as the function and not the results) I believe the appropriate way to solve it is through a promise. (Note, the service itself is using a promise successfully.) However, I'm having difficulty getting the $scope to update when the promise is resolved.
I believe the following code is along the right track. $q is injected in the controller.
...
$scope.updateChildObject = function(args) {
var defer = $q.defer();
defer.promise.then(function() {
return FactoryService.getSomethingFromServer(args);
});
$scope.object.childObject = defer.resolve();
};
...
So can anyone tell my what I'm doing wrong here? Promises are just one of those things that haven't really clicked for me yet.
Just as an alternative to your answer: you say FactoryService is already successfully using a promise, and in that case it seems like you don't need an additional promise in updateChildObject too. You could update FactoryService.getSomethingFromServer(args) to return a promise (i.e. with return defer.promise; at the end and defer.resolve(results); in the async bit), and then simplify updateChildObject to just:
$scope.updateChildObject = function(args) {
FactoryService.getSomethingFromServer(args).then(function(results) {
$scope.object.childObject = results;
}
};
Also, it's worth knowing that Angular 1.2 intentionally breaks automatic promise unwrapping that was in earlier versions: https://github.com/angular/angular.js/issues/4158 . It used to be the case that this code
$scope.updateChildObject = function(args) {
$scope.object.childObject = FactoryService.getSomethingFromServer(args);
};
would work identically to the one above (assuming getSomethingFromServer returns a promise), but not anymore. This might be the issue you're running into with 1.2
Figured out what I was doing wrong. Definitely was a promise issue in that I just wasn't using them correctly. The following solved it:
...
$scope.updateChildObject = function(args) {
var defer = $q.defer();
defer.promise.then(function(results) {
$scope.object.childObject = results;
});
defer.resolve(FactoryService.getSomethingFromServer(args));
};
...
So defer.resolve calls what's to be resolved. promise.then() passes the results to the next action. So simple.
Related
I have a following piece of code:
function getData(foos, bars) {
var generated = {};
return $q(function(resolve, reject) {
var promises = _.map(foos, function(foo) {
return _.map(bars, function(bar) {
return someServiceCall(foo, bar).then(function(data) {
_.set(generated[foo.id], player.id.toString() + '.data', data);
});
});
});
// Join all promises in to a final resolve
$q.all(promises).then(function() {
resolve(generated);
}, reject);
});
}
What I want to achieve is to have all the someServiceCall-s and it's success handlers finished by the time resolve(generated) is called. When I debug this part of the code in the developer toolbar, resolve(generated) is called before the success handler of someServiceCall is called for every promise.
Note: This not always breaks the functionality, as the objects are passed as a reference, so the data is set even if resolve was already called, but I think the functionality would be clearer if all those calls were finished by the time resolve is called.
I just realized my dumb mistake. The two nested maps resulted the following structure:
var promises = [[Promise, Promise][Promise, Promise, Promise]];
While $q.all expects an array of promises:
var promises = [Promise, Promise, Promise, Promise, Promise];
I could easily solve this by replacing the first map call by flatMap:
return _.flatMap(bars, function(bar) {
It's still strange for me that $q.all silently just resolved the promise without an error or warning that the data format is not appropriate.
I hope I can help someone who runs into this problem in the future.
I'm trying to add a utility method to attach notify listeners to Angular's $q promises, which are not provided by default for some reason. The intention is to provide an .update method that is chainable, similarly to the existing API:
myService.getSomeValue()
.then(function() { /* ... */ })
.catch(function() { /* ... */ })
.update(function() {
// do something useful with a notification update
});
Guided by an answer in Get state of Angular deferred? , and seeing from the Angular documentation for $q as well as the source code that catch is simply defined as promise.then(null, callback), I've implemented this config block:
.config(['$provide', function($provide) {
$provide.decorator('$q', function ($delegate) {
var defer = $delegate.defer;
$delegate.defer = function() {
var deferred = defer();
deferred.promise.update = function(callback) {
return deferred.promise.then(null, null, callback);
};
return deferred;
};
return $delegate;
});
}]);
Which kind of works, but it seems like the above decorator doesn't get set up immediately which breaks the chaining interface. The first time a $q.defer() is defined (maybe per block?)
first.promise
.then(function() { /* ... */ })
.update(function() {
// do something useful with a notification update
});
throws a TypeError: first.promise.then(...).update is not a function.
Example here: http://plnkr.co/edit/5utIm0HXpIKsjsA4H9oS
I've only noticed this when I was writing a simple example, I've used this code without issue when the promises were returned from a service and other promises had already been used (if this maybe would have an impact?). Is there any way to get the plunker example to work reliably when chaining immediately?
Deferred and Promise are two different APIs in Angular, and this way only the promise belonging to defer is being decorated, while the promise returned from then is not.
Both of them use Promise() constructor which isn't exposed anywhere on $q. However, Promise.prototype can be modified with
Object.getPrototypeOf(deferred.promise).update = function(callback) { ... };
For an angular project, I have to nest promises and I run into cases where I am not sure of what I am doing.
Here is one of my code :
return Action1().then(function (data) {
var defer = $q.defer();
if (data.condition) {
$q.all([Action2(), Action3(), Action4()]).then(function () {
defer.resolve();
});
} else {
defer.reject("error_code");
}
return defer.promise;
});
Action1, Action2, Action3 and Action4 are working promises functions. It's a lot of promises and actions depend on conditions.
Can I do that and be sure my main function will be always resolved or rejected?
I read that we can pass promise inside resolve function.
Can I do that and is this the same as above:
return Action1().then(function (data) {
var defer = $q.defer();
if (data.condition) {
defer.resolve($q.all([Action2(), Action3(), Action4()]);
} else {
defer.reject("error_code");
}
return defer.promise;
});
No, it is not. Your first function would stay forever pending if one of Action2(), Action3() or Action4() did "throw", and reject the $q.all(…) promise - your deferred is never resolved then. This is the most common bug of the deferred antipattern you've used here.
Your second function does mitigate this, but is still unncessary complicated. You don't need a deferred here at all! Just return the promise directly, and use $q.reject:
return Action1().then(function (data) {
if (data.condition) {
return $q.all([Action2(), Action3(), Action4()]);
} else {
return $q.reject("error_code");
}
});
Or, as this happens inside a then handler, you can also use throw "error_code".
Thanks for your answer, I can see my error on the first code version. I think it's the q.all which perturbs me.
I read the deferred antipattern. It said that we don't have to create deferred objects for no reason.
The simple case is this :
return Action1().then(function () {
return $q.all([Action2(),Action3(), Action4()]);
});
But due to the if (data.condition) I can't do it.
Is my second code the only way to do it? Am I in a case or I have to use defer?
It speaks about "promisification", but with Angular I don't know if it's a good thing (libs seem unmaintained).
Cheers,
I am trying to call a service in angular.js through a controller on load and return a promise. I then expect the promise to be fulfilled and for the DOM to be updated. This is not what happens. To be clear, I am not getting an error. The code is as follows.
app.controller('TutorialController', function ($scope, tutorialService) {
init();
function init() {
$scope.tutorials = tutorialService.getTutorials();
}
});
<div data-ng-repeat="tutorial in tutorials | orderBy:'title'">
<div>{{tutorial.tutorialId}}+' - '+{{tutorial.title + ' - ' + tutorial.description}}</div>
</div>
var url = "http://localhost:8080/tutorial-service/tutorials";
app.service('tutorialService', function ($http, $q) {
this.getTutorials = function () {
var list;
var deffered = $q.defer();
$http({
url:url,
method:'GET'
})
.then(function(data){
list = data.data;
deffered.resolve(list);
console.log(list[0]);
console.log(list[1]);
console.log(list[2]);
});
return deffered.promise;
};
});
Inside of the ".then()" function in the service, I log the results and I am getting what I expected there, it just never updates the DOM. Any and all help would be appreciated.
getTutorials returns promise by itself. So you have to do then() again.
tutorialService.getTutorials().then(function(data){
$scope.tutorials = data;
});
Before that, $http returns a promise with success() and error().
Although you can also use then as well
Since the returned value of calling the $http function is a promise,
you can also use the then method to register callbacks, and these
callbacks will receive a single argument – an object representing the
response.
So you are correct with that.
What is your data coming from the http call look like? Your code works - I created a version of it here http://jsfiddle.net/Cq5sm/ using $timeout.
So if your list looks like:
[{ tutorialId: '1',
title : 'the title',
description: 'the description'
}]
it should work
In newer Angular versions (I think v 1.2 RC3+) you have to configure angular to get the unwrap feature working (DEMO):
var app = angular.module('myapp', []).config(function ($parseProvider) {
$parseProvider.unwrapPromises(true);
});
This allows you to directly assign the promise to the ng-repeat collection.
$scope.tutorials = tutorialService.getTutorials();
Beside that I personally prefer to do the wrapping manually:
tutorialService.getTutorials().then(function(tutorials){
$scope.tutorials = tutorials;
});
I don't know the exact reason why they removed that feature from the default config but it looks like the angular developers prefer the second option too.
I am using some data which is from a RESTful service in multiple pages.
So I am using angular factories for that. So, I required to get the data once from the server, and everytime I am getting the data with that defined service. Just like a global variables. Here is the sample:
var myApp = angular.module('myservices', []);
myApp.factory('myService', function($http) {
$http({method:"GET", url:"/my/url"}).success(function(result){
return result;
});
});
In my controller I am using this service as:
function myFunction($scope, myService) {
$scope.data = myService;
console.log("data.name"+$scope.data.name);
}
Its working fine for me as per my requirements.
But the problem here is, when I reloaded in my webpage the service will gets called again and requests for server. If in between some other function executes which is dependent on the "defined service", It's giving the error like "something" is undefined. So I want to wait in my script till the service is loaded. How can I do that? Is there anyway do that in angularjs?
You should use promises for async operations where you don't know when it will be completed. A promise "represents an operation that hasn't completed yet, but is expected in the future." (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)
An example implementation would be like:
myApp.factory('myService', function($http) {
var getData = function() {
// Angular $http() and then() both return promises themselves
return $http({method:"GET", url:"/my/url"}).then(function(result){
// What we return here is the data that will be accessible
// to us after the promise resolves
return result.data;
});
};
return { getData: getData };
});
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
// this is only run after getData() resolves
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
}
Edit: Regarding Sujoys comment that
What do I need to do so that myFuction() call won't return till .then() function finishes execution.
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
console.log("This will get printed before data.name inside then. And I don't want that.");
}
Well, let's suppose the call to getData() took 10 seconds to complete. If the function didn't return anything in that time, it would effectively become normal synchronous code and would hang the browser until it completed.
With the promise returning instantly though, the browser is free to continue on with other code in the meantime. Once the promise resolves/fails, the then() call is triggered. So it makes much more sense this way, even if it might make the flow of your code a bit more complex (complexity is a common problem of async/parallel programming in general after all!)
for people new to this you can also use a callback for example:
In your service:
.factory('DataHandler',function ($http){
var GetRandomArtists = function(data, callback){
$http.post(URL, data).success(function (response) {
callback(response);
});
}
})
In your controller:
DataHandler.GetRandomArtists(3, function(response){
$scope.data.random_artists = response;
});
I was having the same problem and none if these worked for me. Here is what did work though...
app.factory('myService', function($http) {
var data = function (value) {
return $http.get(value);
}
return { data: data }
});
and then the function that uses it is...
vm.search = function(value) {
var recieved_data = myService.data(value);
recieved_data.then(
function(fulfillment){
vm.tags = fulfillment.data;
}, function(){
console.log("Server did not send tag data.");
});
};
The service isn't that necessary but I think its a good practise for extensibility. Most of what you will need for one will for any other, especially when using APIs. Anyway I hope this was helpful.
FYI, this is using Angularfire so it may vary a bit for a different service or other use but should solve the same isse $http has. I had this same issue only solution that fit for me the best was to combine all services/factories into a single promise on the scope. On each route/view that needed these services/etc to be loaded I put any functions that require loaded data inside the controller function i.e. myfunct() and the main app.js on run after auth i put
myservice.$loaded().then(function() {$rootScope.myservice = myservice;});
and in the view I just did
ng-if="myservice" ng-init="somevar=myfunct()"
in the first/parent view element/wrapper so the controller can run everything inside
myfunct()
without worrying about async promises/order/queue issues. I hope that helps someone with the same issues I had.