Angular js html not updating - javascript

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;
});
});

Related

Can I resolve a promise from a broadcasted event?

We're using angular legacy (1.5)
I am trying to bulk load some layers using a 3rd party library.
I need to wait till they are loaded before continuing.
So in my get data section, it calls the library and asks it to add data, I start a $q.defer in this section and assign this to a factory level variable.
In the service for the 3rd party lib, I setup a count for requests out and requests in, when they match, the $broadcast and event to tell me its complete.
I then listen ($on) for this event and set the promise to resolved.
however the application doesn't wait for this.
I understand this is a strange one, but what can I do.
Our code is quite involved, so I have tried to create crude example of what we are trying to archive.
function layerFactory($rootScope, $log, $q, DataService) {
var factory = {
getData:getData,
var _dataPromise;
function getData(data){
_getLayerData(data).then(function(){
_processData(data);
});
}
function _getLayerData(data){
_dataPromise = $q.defer();
DataService.getData(data) // Treat DataService as a 3rd party lib, this doesn't return a promise. I have no way of knowing this is complete until a $broadcast is sent.
_dataPromise.promise;
}
$rootScope.$on('dataLoaded', function(){
_dataPromise = $q.resolve();
});
}
return factory;
}
This isn't waiting for the promise to resolve and instead going into the 'then' statement and processing the next function 'too early' I need it to wait till the first function as finished.
Any ideas?
Ok, I couldn't find a way to make this work, so what I did instead was to set a factory level variable (boolean) to indicate when the loading had started, this was then set to false when the $on event was triggered.
In my getLayerData method, I set up an internal to run every 500 ms, inside this interval function I run a check for the loading variable, if false (ie loaded), then return a deferred.resolve() and cancel the interval.

AngularJS $q.resolve(), ES6 Promise.resolve() (and other animals)

Disclaimer: there actually two questions being asked here but I feel like they are closely related.
I'm trying to pass a promise object to a directive and I want to run some initialization code in the directive as soon as the promise resolves.
In a controller I have:
$scope.item = $http.get(...)
.success(function (result) {
$scope.item = result.item;
});
$scope.item is passed to a directive (via an attribute in an isolated scope called item).
The directive link function finally do something like:
Promise.resolve(scope.item)
.then(function () {
initialize();
});
This works fine in Firefox but when I run it on IE I get an error because Promise is not defined. This problem made me realize I need to probably use the AngularJS $q service in order to provide consistency among browsers and while I was looking at the documentation I discovered another problem, which seemed small at first but it is actually giving me headaches: the success() function is deprecated and I should use then(successCallback) instead. Easy peasy, I thought, BUT as soon as I change the success call in the controller the code stop working in Firefox too! I cannot figure out why. So this is the first question.
The second question is that (even if I leave the success call in the controller) if I modify the code in the directive link function to use $q with what I thought was the equivalent:
$q.resolve(scope.item, function() { initialize(); });
this still doesn't work at all. Any suggestion?
You need to use Angular's $q not only because it works across browsers - but also because it is deeply linked to Angular's digest cycles. Other promise libraries can accomplish this feat but native promises cannot easily do so.
What $q.when does (the $q version of Promise.resolve) is convert a value or a promise to a $q promise. You don't need to do it since you're already using Angular's own $http API which returns promises already.
I warmly recommend that you put your web calls in services and don't affect the scope directly - and then call those services to update the scope.
The pattern is basically:
$http.get(...) // no assign
.success(function (result) { // old API, deprecated
$scope.item = result.item; // this is fine
});
Or with the better then promise API that has the benefits of promises over callbacks like chaining and error handling:
$http.get(...).then(function (result) {
$scope.item = result.data;
});
You are correct about the .success method being deprecated. The .then method returns data differently than the .success method.
$scope.httpPromise = $http.get(...)
.then(function (result) {
$scope.item = result.data.item;
return result.data;
});
You need to return the data to chain from it.
$scope.httpPromise.then ( function (data) {
//Do something with data
initialize();
});
For more information on the deprecation of the .success method see AngularJS $http Service API Reference -- deprecation notice.
Since scope.item is a promise all you have to do is:
scope.item.resolve.then(function() { initialize(); });
Make sure $q is injected in your directive.
improved answer
As #benjamin-gruenbaum mentioned correctly, I used an anti-pattern in my answer. So the solution is basically to pass the promise to your directive and use it there (as already mentioned in Benjamins answer).
Working jsFiddle: https://jsfiddle.net/eovp82qw/1/
old answer, uses anti-pattern
Sorry if I give you a solution that perhaps differs too much from your code. But maybe you can adopt it to your solution.
My approach would be to create a second promise I handover to your directive. This is imho a cleaner way for resolving the waiting state of the directive and don't reuse the same scope variable for two different tasks.
HTML
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<my-directive promise="promiseFromController"></my-directive>
</div>
</body>
JS
function MyCtrl($scope, $q, $http) {
function init() {
var deferredCall = $q.defer();
// simulated ajax call to your server
// the callback will be executed async
$http.get('/echo/json/').then(function(data) {
console.log('received', data);
deferredCall.resolve(data); //<-- this will resolve the promise you'll handover to your directive
});
// we're return our promise immediately
$scope.promiseFromController = deferredCall.promise;
}
init();
}
angular.module('myApp',[])
.controller('MyCtrl', MyCtrl)
.directive('myDirective', function() {
return {
scope: {
promise: '='
},
controller: function($scope) {
console.log($scope);
$scope.promise.then(function(data) {
console.log('received data in directive', data);
});
}
}
})
Working jsFiddle: https://jsfiddle.net/1ua4r6m0/
(There is no output, check your browser console ;) )

Promise to array with PouchDB and AngularJs ng-repeat

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.

AngularJS - Do you return the promise or data from the service/factory?

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.

Angularjs $http.get in a factory doesn't return valid JSON but a different object

When I call $http.get from a factory it doesn't return the JSON object I need but a different one.
Here's my code:
var cart = angular.module('cart', []);
cart.factory('getItems',['$http', function($http){
return{
get:$http.get('index.php').
success(function(data){
return data;
})
}
}]);
cart.controller("TableCtrl", ['$scope', 'getItems', function($scope, getItems){
console.log(getItems.get);
}]);
now this is the JSON object that it should return:
[{"id":"1","name":"Cap"},{"id":"2","name":"Coat"},{"id":"3","name":"Shoes"}]
And this is what I get when I console.log
d {$$state: Object, success: function, error: function, then: function, catch: function…}
everything runs fine if I run the $http.get inside the controller, but if I place it in a factory it just doesn't.
It's returning a promise, which you need to resolve
In your controller..
getItems.get().then(function(response) {
console.log(response.data)
});
More information, see The Promise Api
Angular $q docs
$http.get returns a promise not the data itself. Your current use of the success function is not going to get data back.
In your factory:
return{ get: $http.get('myurl')};
Then consuming it should be:
myFactory.get().success(response){
var mydata = response.data;
}
Success is just an addition to the promise. I like to just use .then like standard, unadorned promises.
When I first heard about promises, I thought it meant you could use it without having to provide any sort of callback-like code on your consuming end. That is not true, but promises are still super awesome and very powerful. The coolest thing is that you can keep a promise around, and anybody can add a .then to the promise. Whoever does will get their code executed as soon as the asynchronous request finishes, or right away if it has already finished. So promises let you not have to worry about asynchronous behavior, but you still have to use what is basically its more powerful version of callbacks.

Categories

Resources