Digest is not triggered when scope variable is updated in AngularJS - angularjs

I am using AngularJS and angularFire to show a list of my tasks:
<ul ng-repeat="tool in tools">
<li>{{tool.name}} {{ tool.description}}</li>
</ul>
var toolRef = new Firebase(dbRef + "/tools/" + toolId);
toolRef.once('value', function(snapshot) {
console.log(angular.toJson(snapshot.val()));
$scope.tools.push(snapshot.val());
//$scope.$apply();
});
http://jsfiddle.net/oburakevych/5n9mj/11/
Code is very simple: I bind a 'site' object to my Firebase DB. The object contains a list of ID of relevant tools. Then I load every tool in the $scope.tools variable and use ng-repeat to show them in the UI.
However, every time I push a new entry into my $scope.tools - the DOM is not updated. I have to call $scope.$apply to trigger digest after every push - see commented out line 18 and then it works.
It's really strange since I sow this several times now and only with scope variables bound with angularFire.
Can anyone explain this? Am I doing anything wrong?

I am not sure about the Firebase once method as how it work, but it seems like it is making changes to model outside of Angular context in it's callback and hence you need $scope.$apply.
In anuglar services like $http ,$resource, and $timeout internally call $scope.$apply so that you don't need to call it on the callback. If the Firebase system provides a method replacement for once which internally does apply or returns a promise, you can very well skip call to apply method again and again.

Because you change the scope outside of AngularJS, you must use $scope.$apply() to inform Angular about the scope changes. That is a common way to handle that. To use the error handling of AngularJS i would wrap the code in a callback function like:
$scope.$apply(function(){
$scope.tools.push(snapshot.val());
});

As tschiela says, you need to wrap the function. I use $timeout. So...
<ul ng-repeat="tool in tools">
<li>{{tool.name}} {{ tool.description}}</li>
</ul>
var toolRef = new Firebase(dbRef + "/tools/" + toolId);
toolRef.once('value', function(snapshot) {
$timeout(function() {
$scope.tools.push(snapshot.val());
})
});

To update scopes when you change them, you need to use the factory angularFireCollection of angularFire. Otherwise you are just calling a javascript class wich won't update scopes until $apply().
var getTool = function(dbRef,toolId) {
console.log("Gettingtool ID: " + toolId);
angularFireCollection(dbRef + "/tools/" + toolId, function(snapshot) {
console.log(angular.toJson(snapshot.val()));
$scope.tools.push(snapshot.val());
});
};
DEMO

Related

init ng-controller when content is loaded dynamically in angular

I'm learning Angular I tried to init a controller after create a new content by ajax (with jQuery, maybe it's not a good idea but just I'm starting to learning step by step.)
this is my code.
angular.module('app').controller('TestController',function($scope){
$scope.products = [{
'code': 'A-1',
'name': 'Product 01'
}];
$scope.clickTest = function(){
alert('test');
}
});
$.ajax({..})
.done(function(html){
angular.element(document).injector().invoke(function($compile){
$('#content').html(html);
var scope = $('#content').scope();
$compile($('#content').contents())(scope);
});
});
In my html I use ng-repeat but not load nothing, however when I click in
ng-click="clickTest" it works! and ng-repeat is loaded. this is my problem I need that the ng-repeat load when I load for first time.
Thanks.
Sorry for my bad english.
with jQuery, maybe it's not a good idea
Yes spot on
Now getting into your issue:- When you click on element with ng-click on the html, it works because it then runs a digest cycle and refreshes the DOM and your repeat renders. $Compile has already instantiated the controller and methods are available and attached to DOM. But there is one digest cycle that runs in angular after controller initialization, which renders data in DOM, and that does not happen in your case since you are outside angular.
You could do a scope.$digest() after compile to make sure element is rendered and digest cycle is run.
Also probably one more better thing would be to wrap it inside angularRootElement.ready function, just to make sure before the injector is accessed the element is ready, in your case enclosing it inside ajax callback saves you (the time for it to be ready) but better to have it.
Try:-
$.ajax({..})
.done(function(html){
var rootElement = angular.element(document);
rootElement.ready(function(){
rootElement.injector().invoke(function($compile){
var $content = $('#content'),
scope = $content.scope();
$content.html(html);
$compile($content.contents())(scope);
scope.$digest();
});
});
});
Sample Demo
Demo - Controller on dynamic html
Instead of getting html from the server you could create partials and templates, use routing , use angular ajax wrapper with $http etc... Well i would not suggest doing this method though - however based on your question you understand that already. There was a similar question that i answered last day
If you are not looking for a better way to do what you are doing, ignore the following.
The beauty of using frameworks like AngularJS and KnockoutJS (or many more) is that we don't have to worry about the timing of when the data loads. You just set up the bindings between the controller and the UI and once the data is loaded into the respective properties, these frameworks will take care of updating the UI for you.
I am not sure why you are trying to set up the UI using JQuery and waiting for the data to loaded first to do so but normally you will not have to do all that.
You can create a UI with ng-repeat and click bindings ,etc and make an ajax call to get the data from anywhere. Once the data is loaded, in the callback, just push the necessary data into the collection bound to the ng-repeat directive. That is all you will have to do.

Auto-updating scope variables in angularjs

I'm currently playing with AngularJS. I'd like to return, from a service, a variable that will let the scope know when it has changed.
To illustrate this, have a look at the example from www.angularjs.org, "Wire up a backend". Roughly, we can see the following:
var projects = $firebase(new Firebase("http://projects.firebase.io"));
$scope.projects = projects;
After this, all updates made to the projects object (through updates, be it locally or remotely) will be automatically reflected on the view that the scope is bound to.
How can I achieve the same in my project? In my case, I want to return a "self-updating" variable from a service.
var inbox = inboxService.inboxForUser("fred");
$scope.inbox = inbox;
What mechanisms let the $scope know that it should update?
EDIT:
In response to the suggestions, I tried a basic example. My controller:
$scope.auto = {
value: 0
};
setInterval(function () {
$scope.auto.value += 1;
console.log($scope.auto.value);
}, 1000);
And, somewhere in my view:
<span>{{auto.value}}</span>
Still, it only displays 0. What am I doing wrong ?
UPDATE:
I made a demo plunker: http://plnkr.co/edit/dmu5ucEztpfFwsletrYW?p=preview
I use $timeout to fake updates.
The trick is to use plain javascript references:
You need to pass an object to the scope.
You mustn't override that object, just update or extend it.
If you do override it, you lose the "binding".
If you use $http it will trigger a digest for you.
So, whenever a change occurs, the scope variable reference to same object that gets updated in the service, and all the watchers will be notified with a digest.
AFAIK, That's how $firebase & Restangular work.
If you do multiple updates you need to have a way of resetting properties.
Since you hold a reference to an object across the application, you need to be aware of memory leaks.
For example:
Service:
app.factory('inboxService', function($http){
return {
inboxForUser: function(user){
var inbox = {};
$http.get('/api/user/' + user).then(function(response){
angular.extend(inbox, response.data);
})
return inbox;
}
};
});
Controller:
app.controller('ctrl', function(inboxService){
$scope.inbox = inboxService.inboxForUser("fred");
});
It depends on how the object is updating. If it gets updated "within" angular, a digest cycle will be triggered (See http://docs.angularjs.org/guide/scope), and the view will update automatically. That is the beauty of Angular.
If the object gets updated "outside" of angular (e.g. a jQuery plugin), then you can manually trigger a digest cycle by wrapping the code that's doing the updating in an $apply function. Something like this:
$scope.$apply(function() {
//my non angular code
});
See http://docs.angularjs.org/api/ng.$rootScope.Scope for more info.

$apply in setTimeout

I konw that $apply used to connect the Javascript context and the AngularJS context.
A simple example is below:
template:
<div>{{someVal}}</div>
javascript in controller:
setTimeout(function() {
scope.$apply(function(){scope.someVal = 123});
}, 1000);
We need use $apply in above situation.
First Qustion:
If I modify javascript above to:
setTimeout(function() {
scope.someVal = 123;
}, 1000);
scope.$watch('someVal', function(val) {
console.info(someVal);
});
No console about someVal modified to 123... Why? Can't we watch expression modified in timeout callback?
Second Question:
If we use ngSwitch directive like below:
<div ng-switch on="sub">
<div ng-switch-when="a">
//state a
</div>
<div ng-switch-when="b">
//state b
</div>
</div>
When I modify the sub in controller:
scope.sub = 'a';
setTimeout(function() {
scope.sub = 'b';
}, 1000);
No need to use $apply!!!! Why?
I found that ngSwitch directive use $watch to monitor on attribute value. Why ngSwitch can watch scope attribute modified in timeout callback?????
Pls tell me the reason about the 2 qustions above.
From AngularJs documentation
$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.
window.setTimeout is a JavaScript function, so whatever you use setTimeout you must use $apply() to update the model.
Your second example wouldn't work without $apply(), I've made a demo to clarify the $watch and $apply issue. Please check it.
You can use $timeout that is a wrapper for window.setTimeout and also this way you wont need to use $apply on the callback:
$timeout(function() {
scope.someVal = 123; }, 1000);
When you run code that is outside Angular, you'll need a way to let the Angular and the watchers of that value, to know that it has changed. Thats what $apply is for, it will allow watch listeners to fire
About you second question, of why the scope is updating without a $apply, you should be firing indirectly somehow a $apply/$digest. To give you a more specific answer, a Plunker will be necessary to check what else in going on on your code.
Use the Angularjs $timeout service instead of setTimeout and you will not require $apply. Same thing is if you are using a jquery http call, use the angular http service to avoid using $apply.

Force "soft" update of fields with Angularjs

I have an application, call it a "form-filler" that works with many, many sites using Jquery to automatically update fields.
Pseudo Code:
Inject Jquery into the webpage
Discover the required form.
Update the values, e.g.,
$(document).ready(function) {
$('#id').val("some value");
}
I have a new customer who is using Angularjs and this model breaks down as the $scope is obviously being updated "out-of-band". I don't have access to the third party source to make changes, so I was wondering if it is possibly to get a jQuery update to trigger an Angularjs update?
You can use angular.element() to get a hold of the scope and the ngModelController:
var value = 'theNewValue';
var el = angular.element($('#name'));
el.scope().$apply(function(){
el.val(value);
el.controller('ngModel').$setViewValue(el.val());
});
Here is a simple example: http://plnkr.co/edit/OJQQmanwQoFQSgECuqal?p=preview
Agreeing on the other responses, I'd suggest to use $timeout instead of $apply to avoid problems with the digest phase.
Like in #liviu-t response, get hold of the $timeout service by means of the $element's injector. Then use it as it was a nextTick() function. It is in fact (with second argument 0 or missing) almost equivalent to nextTick(), with the difference that it always runs its argument in the digest phase, unlike $apply, which must be called outside of the digest.
It's a tad complicated depending on the actual case. My solution assumes that the elements are available on dom ready and not loaded by angular using partials. DEMO
JS
function setAngularValue($elem, value) {
var scope = $elem.scope();
var ngModelName = $elem.attr('ng-model');
if(ngModelName) {
var $injector = $elem.injector();
//get the parse service to use on the ng-model
var $parse = $injector.get('$parse');
var ngModel = $parse(ngModelName);
scope.$apply(function() {
//this will allow any ng-model value ex: my.object.value
ngModel.assign(scope, value);
});
}
else {
//fallback if there is no model, weird case imho
$elem.val(value);
}
}
$(document).ready(function() {
var $elem = angular.element('#myJqueryId');
var value = 'some value';
setAngularValue($elem, value);
});
HTML
<p>Hello {{my.angular.model}}!</p>
<input id="myJqueryId" ng-model="my.angular.model"></input>
LINKS
$injector
$parse

Access $scope.property outside of a callback function in a controller

Can't think of a better title, sorry.
Please consider the following code -
//controller
function ProductCtrl($scope) {
getCategories(function (result) {
$scope.category = result.categories[0].name;
}); // asynchronouse method; getCategories calls uses XHR and returns the result as callback.
}
//view
{{category}}
When the view loads in the browser, getCategories gets called immediately. How do I make it load on demand, like onLoad on div or something so I can re-use this method somewhere else? Something like, $scope.getCategories returns the data I wanted, not on controller load. And how do I use this method in the view? e.g. <div onload=getCategories()></div> works?
Another question, the view does not print the category. How do I make $scope.category available outside of getCategories?
When the view loads in the browser, getCategories gets called immediately. How do I make it load on demand?
Wrap it in a function, and call that function when appropriate:
$scope.getCategories = function() {
getCategories(function (result) { ... }
}
so I can re-use this method somewhere else?
If multiple views need access to the result of getCatetories, you should create a service that stores the result. Your controllers can then inject that service to get access to it.
the view does not print the category.
Your AJAX is happening "outside" of Angular, so Angular doesn't know that a callback is being called and that $scope.category is being updated. Simply add $scope.$apply() after you update $scope.category in your callback and Angular will run a digest cycle and update your view.

Resources