AngularJS promises are not invoking `then` callback when completed - angularjs

I have something like a master controller that sets some stuff in the scope, so the inner controllers can use it.
That setup work is asynchronous, so I've wrapped it in a promise, but it's not executing it's callback unless it was already resolved (I tried setting a breakpoint, and if I wait enough, it actually runs the then callback).
Here's a fiddle that reproduces my problem with a timeout rather than a network request: http://jsfiddle.net/LMv8v/1/
HTML
<div ng-app>
<div ng-controller="configController">
<div ng-controller="testC">
{{test}}
{{foo}}
</div>
</div>
</div>
Javascript
function configController ($scope, $q) {
var deferred = $q.defer();
$scope.config = deferred.promise;
setTimeout(function() {
console.log('timeout');
deferred.resolve({
'foo' : 'baz'
});
}, 1000);
};
function testC($scope) {
$scope.test = 'I am working, uh?';
$scope.config.then(function(config) {
console.log('then...');
$scope.$apply(function() {
$scope.foo = config.foo;
});
});
};
It shows the 'timeout', but not the 'then...' message.
(I know that this would be better suited for a Service, but I already have plenty of code with the nested scopes and I want to get it working before I start refactoring)

If you are using $.getJSON() (from jQuery I am guessing)
You will run into a similar issue where you are resolving something outside of the Angular world, try the following.
$.getJSON('ajax/test.json', function(data) {
$scope.$apply(function(){
deferred.resolve({
'foo' : 'baz'
});
});
});
Example on jsfiddle with jQuery ajax

Related

Why is Angular not updating with a JSON file?

I'm trying to use a simple Angular JS app to load data from a JSON file to a website but it does not work.
The JSON file is:
{"a": "a"}
The Angular app is:
var app = angular.module("app", [])
.controller("ctrl", ["ser", function(ser) {
var vm = this;
ser.getInfo().then(function(data) {
vm.data = data;
});
}])
.service("ser", function() {
this.getInfo = function() {
return $.get("models/model.json");
};
});
The HTML is:
<div ng-controller="ctrl as ctrl">
<p>{{ctrl.data.a}}</p>
</div>
I'm not getting any console errors. I think the problem is related to the lexical scoping for the controller due to the asynchronous getInfo().then() call in the controller, I checked vm inside the function and it is being loaded correctly but doesn't seem to change the ctrl object or Angular is not updating when it does.
I'm serving the app locally.
It works sometimes but most times it doesn't. I can get it to work using $scope but I'm trying to figure out why it's not working now.
It appears you are using jQuery for the ajax. If you modify the scope outside of angular context you need to notify angular to run a digest
Change to using angular $http to avoid such issues
var app = angular.module("app", [])
.controller("ctrl", ["ser", function(ser) {
var vm = this;
ser.getInfo().then(function(response) {
vm.data = response.data;
});
}])
.service("ser", ['$http', function($http) {
this.getInfo = function() {
return $http.get("models/model.json");
};
}]);
DEMO
If it works with $scope that means that without it, Angular is not aware that you performed an asynchronous operation.
I think the following line is using jQuery: return $.get("models/model.json");
So even if you get your data from your function getInfo, it isn't synchronized with the view via vm.data = data;

Why do I have to use $promise with $resource?

Admittedly I am relatively new to Angular but I've come unstuck using ngResource for a REST call.
I followed a factory pattern which seems to be relatively common.
However my I only get access to the REST response by using a $promise.then. Which I understand you do not require with $resource.
So I have this factory...
var LookupsService = angular.module('LookupsService', ['ngResource']);
LookupsService.factory('Lookups', ['$resource',
function ($resource) {
return $resource('http://localhost:5001/api/lookups', {});
}]);
And this fails (as undefined)
alert(Lookups.get().foo);
But this is fine
Lookups.get()
.$promise
.then(function (lookups) {
alert(lookups.foo);
});
I'm obviously missing something here. Some kind of schoolboy error ;-)
UPDATE
Thanks for all your help - it is now very clear. I gave #fikkatra the tick for the clarity of her answer and snippet. But pretty much all the answers were helpful. I must be careful using Alert for debugging!
Using alert on a promise won't work, i.e. it won't show the results within the promise. However, angular has been designed to use the $resource service directly to bind to the scope. This means you can bind a $resource result to a scope object, and angular will take into account that the scope object is a promise when applying the binding. That's why alert(resourceResult) won't work, but $scope.myObj = resourceResult; (and then bind it in a view), will work.
Inserted code snippet to explain things more clearly:
var app = angular.module('myapp', ['ngResource']);
app.controller('myctrl', function($scope, $resource) {
//this won't work:
//alert("won't work: " + $resource('https://api.github.com/users/fikkatra').get());
//but this will work
$resource('https://api.github.com/users/fikkatra').get().$promise.then(function(user) {
alert("will work: " + user.login);
});
//directly assigning $resource result to scope will work:
$scope.user = $resource('https://api.github.com/users/fikkatra').get();
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-resource.js"></script>
<div ng-app="myapp" ng-controller="myctrl">
{{user.login}}
</div>
You don't have to, and shouldn't need to, use $promise with $resource. The $resource service can be used like this:
(function() {
'use strict';
angular
.module('app')
.factory('MyService', MyService);
MyService.$inject = ['$resource'];
function MyService($resource) {
return $resource('my/url/:id', {id: "#id"}, {
'query': { isArray: false },
'update': { method: "put" }
});
}
})();
It gives you back several HTTP methods that you can use in your code without you having to write them yourself. To use them you can do something like this:
function getActionTypes(){
MyService.query(
function success(result){
// do stuff with result
},
function failure(result){
console.error(result);
});
}
You actually don't have to use the $promise, it's just available if you want to.
Codepen: http://codepen.io/troylelandshields/pen/bpLoxE
angular.module('app', ['ngResource'])
.service('exampleSvc', function($resource){
return $resource('http://jsonplaceholder.typicode.com/posts/1', {});
})
.controller('exampleCtrl', function(exampleSvc, $scope){
$scope.result = exampleSvc.get();
exampleSvc.get().$promise.then(function(val){
//Do some other logic here on the val
$scope.promiseResult = val;
});
});
You can also assign the result of .get() to a variable on the scope and the result will be filled in when the request is resolved (in the first example above). This works well for assigning data to an object on your scope that will just end up being displayed on the page, but might not work well for other situations where you need to use the result of the .get() to do something else.
According to the specification you can pass in a success callback as outlined in this little code snippet.
var User = $resource('/user/:userId', {userId:'#id'});
var user = User.get({userId:123}, function() {
user.abc = true;
user.$save();
});
The success function callback will be called when the resource finishes.
Alternatively, you can use the promise syntax with get(), except you don't need to add your own promise as per your example. Instead you can simply do the following:
Lookups.get()
.then(function (lookups) {
alert(lookups.foo);
});
This is because get() returns a promise. When the promise is evaluated successfully then() will be called

Angular: transfer data with $scope

Really have no idea why this doesn't work. I must be doing something incredibly stupid.
Here is a controller:
angular.module('nightlifeApp')
.controller('TestCtrl', function($scope) {
$scope.testvar = 'before';
setTimeout(function() {
$scope.testvar = 'after';
}, 2000);
});
and here is the view that has this as the controller:
h1(ng-bind='testvar')
h1 {{testvar}}
But neither h1 element ever changes! Any thoughts?
If you're using setTimeout then you manually need to trigger apply. Like
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the angular framework we need to perform proper scope life cycle of
exception handling, executing watches.
setTimeout(function() {
$scope.$apply(function() {
$scope.testvar = 'after';
});
}, 2000);
In my opinion you should use $timeout service. So it will trigger $apply() automatically. Your code will look like
$timeout(function () {
$scope.testvar = 'after';
}, 2000);
Make sure you've injected $timeout service in your controller. In your HTML you don't need to use ng-bind. You're doing same in controller. Only
<h1> {{testvar}} </h1>

AngularJs variable not updating

Not able to figure out what the bug in this code is.I've tried to only post the relevant parts of the code here.
Controller
myApp.controller('MessageCtrl', function ($scope, notificationService, $rootScope) {
$scope.notificationService = notificationService;
$scope.msgCount = 0;
$scope.notificationService.subscribe({channel : 'my_channel'});
$rootScope.$on('pubnub:msg',function(event,message){
$scope.msgCount = $scope.msgCount + 1;
//$scope.$digest();
});
});
My Notification Angular Service
myApp.factory('notificationService',['$rootScope', function($rootScope) {
var pubnub = PUBNUB.init({
publish_key : '..',
subscribe_key : '..'
});
var notificationService = {
subscribe : function(subscription) {
pubnub.subscribe({
channel : subscription.channel,
message : function(m){
$rootScope.$broadcast('pubnub:msg', m);
}
});
}
};
return notificationService;
}]);
And the template :
<div>
Count = {{msgCount}}
</div>
The problem :
Using console logs & using karma tests I have confirmed that the $rootScope.$on method in MessageCtrl is getting called when I do a $broadcast from Notification Service. And that the msgCount variable is getting incremented. However, I don't see the updated value being reflected in the template without running a $scope.$digest() . I am pretty sure I shouldn't be needing to have to call $scope.$digest , ie Angular should be providing me this binding.
Interestingly, when I tried a $rootScope.$broadcast from another controller, the msgCount in the template got incremented without having to call $scope.$digest().
Can anyone kindly help me here. Thank you.
Update
Thanks to Peter and looking at the google group discussion, wrapping the $broadcast in an $apply did the trick.
$rootScope.$apply(function(){
$rootScope.$broadcast('pubnub:question', m);
});
It seems that your $broadcast happens outside AngularJS and you need to notify your app about it with calling $apply(), but better do it in the notificationService.
As for $broadcast and $on trigger a apply/digest you can read in this post. Brief overview of AngularJs source files make me sure that $broadcast does not auto-apply changes (look here ). $broadcast just calling listeners and nothing else.
Please, take a look at this simple example on jsFiddle .
The template
<div ng-controller="myCtrl">
<p>Count: {{ count }}</p>
<button ng-click="fireEvent()">Fire Event</button>
</div>
The controller
angular.module("app", [])
.controller('myCtrl', function($scope, $rootScope, notificationService) {
$scope.count = 0;
notificationService.subscribe();
$rootScope.$on('event', function() {
console.log("event listener");
$scope.count++;
});
$scope.fireEvent = function() {
// it is ok here due to ngClick directve
$rootScope.$broadcast('event', true);
};
})
And factory
.factory('notificationService',['$rootScope', function($rootScope) {
var notificationService = {
subscribe : function() {
setInterval(function(){
console.log("some event happend and broadcasted");
$rootScope.$broadcast('event', true);
// angular does not know about this
//$rootScope.$apply();
}, 5000);
}
};
return notificationService;
}]);
Of course in both cases you will see that event listener fires, but ngClick fires $digest and your notificationService does not.
Also you can get some info about sources that will start the digest cicle in this nice answer https://stackoverflow.com/a/12491335/1274503

Angularjs: scope.data is undefined inside directive

First off, i found the api address from this topic:
Laravel 4 and Angular JS and Twitter Bootstrap 3 Pagination
Now i am working about this, my little script is so:
var app = angular.module('kategori', [
'ngResource',
'apiBaseRoute'
]);
app.factory('Data', ['$resource', 'apiBaseRoute', function($resource, config){
return $resource('http://develop.alexei.me/careers/careers.php?callback=JSON_CALLBACK&page=:page', {
page: 1
}, {
'get': {
method: 'JSONP'
}
});
}]);
app.controller('KategoriListCtrl', function($scope, Data){
$scope.init = function() {
Data.get({}, function(response){
$scope.kategoriList = response.careers;
},function(error){
console.log("HATA VAR" + error);
});
};
});
app.directive('paginate', function(){
return{
scope:{ allData: '=paginate2' },
link: function(scope){
console.log(scope);
}
}
});
And this is the html side :
<div class="col-md-6 col-md-offset-3" ng-controller="KategoriListCtrl" ng-init="init()">
{{kategoriList}}
<div paginate paginate2="kategoriList"></div>
</div>
as you see, console.log(scope) inside directive is shows a lot of things in console, especially i see allData there with lots data, but if i change it to
console.log(scope.allData)
it prints undefined..
i don't understand why. how can i solve this? thanks.
By the time JS reaches your console.log the allData property is undefined (since kategoriList is undefined). kategoriList (and thus allData) is created (and populated with lots of data) asynchronously at a later time.
So, why do you see the data when logging the scope object instead ?
At the time the object is logged it has no property allData (and no data).
But by the time you go over to the console and expand the node and look for the allData property, the property has been added and populated by your AJAX call (using $resource).
It is not clear what you want to do with allData.
If you want to use it in e.g. ng-repeat you don't have to worry: You can use it normally (as if it were defined) and Angular will automatically "pick it up" as soon as it arrives and do stuff.
Yet, if you want (for your own mysterious reasons) to get informed when it is ready, your can use $watch:
scope.$watch('allData', function(newValue) {
if (newValue !== undefined) {
console.log(scope.allData);
}
});
See, also, this short demo.

Resources