I'm reading the allDocs() from a PouchDB database into an AngularJS variable:
var db = pouchService.db;
$scope.recordlist = db.allDocs({startkey: 'move_', endkey: 'move_\uffff', include_docs: true});
console.log($scope.recordlist);
I've noticed that it returns a promise, and when I try to read the array (and properties of the objects inside the array) using ng-repeat, it can't actually access the results, I guess because they are deeply nested.
<div class="row msf-row"
ng-repeat="record in recordlist | filter: shouldShow"
ng-class="{ 'msf-cancelled': record.cancelled, 'msf-commented' : record.comment}">
<div class="col-md-1">{{record.time}}</div>
</div>
Is there any way to turn this promise into a simple array of objects?
I have LoDash loaded in the app also, I don't know if it could be of use.
Assign the array after the promise was fulfilled (or show an error if an error happened):
$q.when(db.allDocs({startkey: 'move_', endkey: 'move_\uffff', include_docs: true}))
.then(function (recordlist) {
$scope.recordList = recordList;
console.log($scope.recordlist);
})
.catch(function (error) {
console.error(error);
});
Update:
As Matt pointed out in the comments, if you're not using angular-pouch angular-pouchdb wrapper then you will need to wrap the action in a $scope.$apply() to trigger the digest cycle or first convert the promise to an angular promise with $q.when(). I think converting the promise to an angular promise would also take care of logging errors, but you should always handle errors (show the user a message). You could do this of course with a global error handler.
What you are doing is accessing the promise, and the not the promise results. While allDocs does indeed return a promise, it is not an angular promise, so you should also wrap the promise in a when to get an actual angular promise.
var pouchPromise = db.allDocs({startkey: 'move_', endkey: 'move_\uffff', include_docs: true});
$q.when(pouchPromise).then(function(recordList){
$scope.recordList = recordList;
console.log($scope.recordlist);
});
I would read up on how promises work here.
It should be noted that this method of utilising pouch is outlined in the actual pouchDB docs here: http://pouchdb.com/api.html
Specifically:
Using Ionic/Angular? You can wrap PouchDB promises in $q.when(). This will notify Angular to update the UI when the PouchDB promise has resolved.
This will allow you to avoid using $scope.$apply() when dealing with the asynchronous nature of non angular promises.
Related
I wrote a simple graphql query that fetches an array of objects. The array is showing up when I do console.log(). However, the array does NOT update the html when the data is fetched. I need to click on the screen for it to update.
I am using the Angular JS stack along with graphql. It seems though that the issue is to do with angular js only and not the API call.
The following is the api call in the JS:
graphql("...").then(
result => {
$scope.data = result.data;
});
HTML:
<div>{{data.length}}</div>
A cleaner approach is to convert the third-party promise to an AngularJS promise with $q.when:
$q.when(graphql("...")).then(
result => {
$scope.data = result.data;
});
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc...1 Since the promise comes from outside the AngularJS framework, the framework is unaware of changes to the model and does not update the DOM.
$q.when
Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
— AngularJS $q Service API Reference - $q.when
Try to add scope.$apply();. Like this:
graphql("...").then(
result => {
$scope.data = result.data;
$scope.$apply();
});
A better approach over $apply is $timeout.
The $timeout does not generate error like "$digest already in
progress" because $timeout tells Angular that after the current cycle,
there is a timeout waiting and this way it ensures that there will not
any collisions between digest cycles and thus output of $timeout will
execute on a new $digest cycle.
graphql("...").then(
result => {
$timeout(function() {
$scope.data = result.data;
});
});
This question already has answers here:
Why are AngularJS $http success/error methods deprecated? Removed from v1.6?
(2 answers)
Is this a "Deferred Antipattern"?
(3 answers)
Closed 4 years ago.
I am currently using $q service from angular to make API calls like this:
var deferred = $q.defer();
$http.get(config.apiHost + details.url)
.success(function (data) {
deferred.resolve(data);
}).error(function (msg) {
deferred.reject(msg);
});
return deferred.promise;
but we can also use this approach also without using $q:
return $http.get(config.apiHost + details.url)
.success(function (data) {
return data;
}).error(function (msg) {
return msg;
});
and as $http itself returns the promise, I can also use more simplified approach:
$http.get(config.apiHost + 'posts')
.success(function (data) {
console.log(data)
}).error(function (msg) {
console.log(msg);
});
So what is the difference between all these specially between $q and $http, as both returns promise and both are async ? Does angular provides some additional functionality with $q ?
I am not able to find any good answer.
$http uses $q, the first example is redundant, and so is the second. You just need to return the promise that $http.get returns:
return $http.get(config.apiHost + details.url);
The above is the same as your first piece of code.
In addition, return msg is not the same as deferred.reject(msg). The equivalent would be throw msg or return $q.reject(msg)
Another thing to note is that success and error are non-standard, you want to use then and catch.
$q is mainly only used for compatibility with libraries that don't support promises by default and when you can't rely on a native implementation of Promise (for example - in older browsers like IE9). There's no reason (for you) to use it otherwise. An example would if you wanted to make a promise-based $timeout. $http itself uses $q under the hood for these exact reasons.
Unlike what other (since deleted) answers have suggested, you do not need to use $q in order to "store" the result of the $http promise. I would not recommend storing the promise at all (as this tends to lead to spaghetti code), but if you must absolutely do this, you can just store the resultant promise from $http; promises only ever execute once.
Any functions passed to then after a promise has resolved/rejected will be resolved on the next tick, without re-invoking the original action that created the promise in the first place - IOW, the result of the promise is memoized within that object.
Also note that promises chain, which is abit out of scope for this answer, but it essentially means that the following pieces of code are equivalent
function legacyGet() {
const deferred = $q.defer()
$http.get('http://www.google.com')
.then((response) => deferred.resolve(Object.assign(response, {foo: bar}))
.catch((error) => deferred.reject(error))
return deferred.defer
}
function modernGet() {
return $http.get('http://www.google.com')
.then((response) => Object.assign(response, {foo: bar}))
}
To summarise: Your title is wrong. We don't prefer using $q, we only use it sparingly. Prefer to use an ES6 Promise unless you need to support browsers that don't have it and you can't use a polyfill.
In angular mostly all the services returns promises only, but there are some instances where you would like to create your own deferred object using $q.
Case 1
When you are using a library which does not support promise or you have created your own function and want to return a promise.
Case 2
When you are using any construct which by default returns a promise but you want to return a separate promise based on some on some condition.
Example: In angular $http returns a promise only but now if you want that if the response of this promise contains a particular value then only you want to return resolved else return failure then you need to create your own deffered object and need to resolve or fail it based on the value returned by $http response.
Could someone explain to me what this code does? I know that it fetches countries and push them to the list which is shown in a web page, but why? I think that $scope.countries = service.query() is enough or is this way to avoid Asynchronous Problems?
$q.all([$scope.address.$promise, $scope.countrys.$promise]).then(function() {
if (!$scope.address.country.id) {
return $q.reject();
}
return Country.get({id : $scope.address.country.id}).$promise;
}).then(function(country) {
$scope.countrys.push(country);
});
Descriptions in code comments. This is conditionally data loading process using promise mechanism. Try to specify your question more detailed if is is not enough for you
// if address and countries was loaded (probably from ajax request)
$q.all([$scope.address.$promise, $scope.countrys.$promise]).then(function() {
// if address doesn't exist reject all actions
if (!$scope.address.country.id) {
return $q.reject();
}
// otherwise load country based on address field as a promise
return Country.get({id : $scope.address.country.id}).$promise;
}).then(function(country) {
// when loading process is finished add country to dataset
$scope.countrys.push(country);
});
According to $q service documentation
$q.all([promise1, promise2, ...])
Combines multiple promises into a single promise that is resolved when
all of the input promises are resolved.
service.query() will inevitably return a promise, which resolves when the asynchronous call completes. It would make no sense to set the scope property (which you will no doubt bind to some form of list) with a promise.
What the code is doing is waiting for the promise(s) to resolve, performing some logic, and setting the resulting data to a scope property.
In my controller, I use a method from a factory to update some data. For example, I'm trying to fetch an updated array of users. Should I be returning the promise itself from the factory? Or should I be returning the data from the promise (not sure if I phrased that correctly)?
I ask because I've seen it both ways, and some people say that the controller shouldn't have to handle whether the request was successful or if it failed. I'm specifically talking about the promise returned from $http in this case.
Or maybe in other words, should I be using the then() method inside the factory, or should I be using it in the controller after returning from the factory?
I've tried to handle the success and error callbacks (using the this() method) from within the service, but when I return the data to the controller, the users array is not properly updated. I'm assuming that's because of the request being async. So in the controller, it would look something like this:
vm.users = userFactory.getUsers();
If I handle the promise from within the controller, and set the users array within the then() method, it works fine. But this goes back to where I should be using then():
userFactory.getUsers().then(
function(data){
vm.users = data;
}, ...
Hopefully someone would be able to shed some light on this or provide some input. Thanks!
There's no way you can return the data from the factory (since it's an async call) without using either a callback approach (discouraged):
userFactory.prototype.getUsers = function(callback){
$http.get('users').then(function (response) {
callback(response.data);
});
};
Or the promise approach.
If you're worried about handling the errors on the controller, then worry not! You can handle errors on the service:
userFactory.prototype.getUsers = function(){
return $http.get('users').then(function(response) {
return response.data;
}, function(error) {
// Handle your error here
return [];
});
};
You can return the results of then and it will be chained. So things from service will execute and then, later on, Controller ones.
I have no problem with controller deciding what to do basing on response failing/succeding. In fact it lets you easily handle different cases and doesn't add a lot of overhead to the controller (controller should be as small as possible and focused on current task, but for me going different path whether request failed is the part of its task).
Anyway, in Angular HTTP requests are wrapped in promises internally (I remember that in the previous versions there was a way to automatically unwrap them), so returning from service/factory always returns a promise, which has to be resolved.
I prefer returning a promise from a service/factory because I tend to let other classes decide what to do with the response.
I have a controller that needs to retrieve two separate REST resources that will populate two dropdowns. I would like to avoid populating either of them until both $http.get() calls have returned, so that the dropdowns appear to be populated at the same time, instead of trickling in one after the other.
Is it possible to bundle $http.get() calls and elegantly set the $scope variables for both returned arrays, without having to write state logic for both scenarios, e.g. a returns before b, b returns before a?
The return value of calling the Angular $http function is a Promise object using $q (a promise/deferred implementation inspired by Kris Kowal's Q).
Take a look at the $q.all(promises) method documentation:
Combines multiple promises into a single promise that is resolved when
all of the input promises are resolved.
Parameters
promises – {Array.<Promise>} – An array of promises.
Returns
{Promise} – Returns a single promise that will be resolved with an array of values, each value corresponding to the promise at the same index in the promises array. If any of the promises is resolved with a rejection, this resulting promise will be resolved with the same rejection.
You can use $q.all to "join" the results of your http calls, with code similar to:
app.controller("AppCtrl", function ($scope, $http, $q) {
$q.all([
$http.get('/someUrl1'),
$http.get('/someUrl2')
]).then(function(results) {
/* your logic here */
});
}
do you mean something like this:
function someController( $scope, $http, $q ) {
var first_meth = $http.get("first_url"),
second_meth = $http.get("second_url");
$q.all([first_meth, second_meth]).then(function(all_your_results_array) {
//here you'll get results for both the calls
});
}
Ref: Angular JS Doc
You could use the Async javsscript library here: https://github.com/caolan/async.
Use the series call. It will make the 2 calls and then call one callback when both are done.