Wait for $scope to really apply changes in html - angularjs

I'm having a problem trying to detect when the $scope variable really affect the html.
I need to access to the element after the $scope really apply the changes to the html, but the $scope.$watch is not doing the trick.
JS:
$scope.variable = "<p id='myElement'>Hello world!</p>";
console.log(jQuery('#myElement')[0]);
HTML:
<div ng-bind-html="varible | trustAsHtml"></div>
The problem is that, even if the $scope is applying the new content, the element still doesn't exist and jQuery returns undefined, so i need to wait untill the $scope really apply the changes to the html.
I know that a solution could be the $timeout but it's not really reliable, there is something that could help me?

Dunno why you need jQuery - there is probably an easier way with angular but there you go: angular has private method called $$postDigest, since all the DOM and model changes happens during $digest cycle it should do what you want, the other method would be to use $timeout, I've made working plunker for you
http://plnkr.co/edit/oZ2xfoUQxcOzNw7eXCmT?p=preview
$scope.htmlvar = $sce.trustAsHtml('<strong>Test</strong>');
$timeout(function () {
console.log('timeout', jQuery('strong').text())
}, 0, false) //false to not run another digest
$scope.$$postDigest(function () {
console.log('postDigest', jQuery('strong').text())
})

Related

Why do we need $scope.apply() when do a change from console but not otherwise?

Say there is an AngularJS controller like this:
var module = angular.module('myApp', []);
module.controller('TimeCtrl', function($scope, $interval) {
var tick = function() {
$scope.clock = Date.now();
window.scope = $scope;
}
tick();
$interval(tick, 1000);
});
Changing $scope.clock would automatically reflect in the DOM.
However, when I do it in the console, I need to do explicitly do
$scope.apply. Why is it so? Is $interval doing some magic?
In general, don't I need to $scope.watch these variables? I thought the purpose of $scope.watch was this.
1.
$interval(f, time)
is more or less
setInterval(function() {
f();
$rootScope.$apply();
}, time)
2.
<div>{{test}}</div>
is more or less
$scope.$watch('test', function(value) {
divElement.innerHTML = value;
})
Why is it so? Is $interval doing some magic?
Yes, it is. It is triggering automatically the AngularJS digest cycle, something you do "manually" with the $scope.$apply() (which also triggers it). This causes the changes to be reflected in the DOM. If the digest cycle is not triggered, AngularJS "does not know changes have been made in the model, so it does not update the DOM".
(...) don't I need to watch these variables?
No, unless you need to be notified when any of these variables have changed their values. As long as you do all changes inside the AngularJS scope the DOM will always be "notified" (updated).
How to know when I am doing the thing inside the AngularJS scope?
Usually when you use functions provided by services such as $interval and $timeout, you're doing things inside the scope, because these are wrappers of the original (setInterval and setTimeout) and trigger automatically the mentioned digest cycle, keeping things synced between model and DOM.
So, finally,
Why do we need $scope.apply() when doing a change from console but not
otherwise?
Because from console you are doing some changes outside the AngularJS scope and need to trigger the mentioned digest cycle by yourself.

AngularJS watching an expression

Say we have this expression
$scope.showButton = users.is_admin() && !document.is_provided;
And then in the same controller you have a button that updates the value of Document.is_provided:
<button ng-click="document.is_provided = true;">Provide document</button>
The problem is that $scope.showButton should now changed but it's not changing.
Updated:
Plnkr showing simplified issue: http://plnkr.co/edit/Qk2jnHAmqsrNXSAjDYEk?p=preview
I'd like to add on to squiroid's answer and explain to you why it works. I'll provide a very simple way for you to understand, as this was a very common problem for me too when I started.
Angular has something called a Digest cycle. It is a series of functions that are called successively to make sure that if a variable A is two-way bound to another variable B (using ng-model for example), they always stay in sync (that is, always have the same value). Functions that angular provides (or services, that usually start with $, like $timeout etc), automatically start the digest cycle
So let's say that you have a variable A and you've bound it to the $scope variable B, which is equal to a variable C. You would expect C to be bound to A too, right? Because you think when you change C, B should change, and so, A should change too. But that is only possible when you start the digest cycle. The cycle will look for variables that need to be updated, and will update them. However, by simply changing the value of C, you will never trigger the digest cycle, and so your change will never propagate to A.
What you do when you put a variable in $watch is (as the $ suggests), you forcefully start a digest cycle. So now when you change C, you start the cycle, it tracks down that it needs to update A too, and that's why it works.
In your example, $scope.showButton is bound to document.is_provided. Now you're using a non-angular function to change the value of document.is_provided. This means that you do not trigger the digest cycle, and that means your change will never be propagated to $scope.showButton. Therefore, showButton always remains false.
And of course, an example to help you understand:
http://jsbin.com/vojoso/edit?js,console,output
Watch the console. I've added logs to help you understand.
Comment out the $watch statement and see what happens.
Try understanding it and then doing it yourself, you'll get it.
Hope I helped :)
I am not sure but you can watch on it:-
$scope.$watch(function(){
return Users.is_admin() && !Document.is_provided;
},fuction(newVal){
$scope.showButton =newVal;
});
Hope it helps :)
Here is good article about $scope.$watch, it should help to understand how to solve your problem.
It you need bigger answer add code which will explain Document variable, and your REST service.
UPD: I see you changed you original question. I suppose you have trouble with controller. Try do not use $scope implicitly, use data attribute as sad angular code style
Also show the part in you template where you connect controller.
UPD 2: You have some misunderstanding of my words, so I modified your plunker example
index.html
<html ng-app="plunker">
<body ng-controller="MainController as main">
<div ng-show="main.data.document.is_provided">visible</div>
<button ng-click="main.hide()">Hide</button>
<script data-require="angular.js#1.4.x"
src="https://code.angularjs.org/1.4.1/angular.js"
data-semver="1.4.1"></script>
<script src="app.js"></script>
</body>
</html>
app.js
angular
.module('plunker', [])
.controller('MainController', MainController);
function MainController() {
var main = this;
this.data = {
document: {
is_provided: true
}
};
this.hide = hide;
function hide() {
main.data.document.is_provided = false;
}
}
You have one problem inside ng-click, as there should be function execution. Also you are using ng-if instead of ng-show.
The best way (imho) to deal with this kind of problems:
Use {{ anyObject | json }} in your view!
This way you know if the reference you are using and the object exist in your scope. using | json gives you accolades {} when your object exists but when it is empty. If your object isn't available in your scope noting is 'printed'.
In your case, change the body tag to:
<body ng-controller="MainCtrl as mainCtrl">
Then, inside the html of this controller add:
DATA: {{mainCtrl | json}}
So now you see your variables! And you also see clicking on the button doesn't change the boolean variable.
With some extra debugging and rewriting, this works:
app.controller('MainCtrl', function($scope) {
var main = this;
this.data = {
document: {
is_provided: true
}
};
$scope.hide = function() {
console.log(main);
main.data.document.is_provided = !main.data.document.is_provided;
}
});
And:
<button ng-click="hide()">Hide</button> (note the brackets)

How does $scope.apply() work exactly in AngularJS?

I often updated model variables corresponding to DOM expression ({{}}) within the controllers. e.g.
$scope.myVar = new_value;
Some times the corresponding DOM expression {{myVar}} is updated automtically, others it's not.
I understand that sometimes we need to call $scope.$apply but...
I don't understand when I should call it
Some times I call it (let's say, just to be "sure") but I get this error (I guess since it's already being executed):
Error: [$rootScope:inprog]
http://errors.angularjs.org/1.3.6/$rootScope/inprog?p0=%24digest
Any clue?
Apply essentially "refreshes" your front end with the changes that had occurred to your scope.
Most of the time you dont need to do apply as it already is done for you.
Lets say that you do an ng-click(); Apply is done for you.
However, there are cases where apply is not triggered, and you must do it yourself.
Example with a directive:
.directive('someCheckbox', function(){
return {
restrict: 'E',
link: function($scope, $el, $attrs) {
$el.on('keypress', function(event){
event.preventDefault();
if(event.keyCode === 32 || event.keyCode === 13){
$scope.toggleCheckbox();
$scope.$apply();
}
});
}
}
})
I have made changes to my scope but no apply was done for me thus i need to do it myself.
Another example with a timeout:
$scope.getMessage = function() {
setTimeout(function() {
$scope.message = 'Fetched after two seconds';
console.log('message:' + $scope.message);
$scope.$apply(); //this triggers a $digest
}, 2000);
};
Understanding apply and digest
A good way to understand the purpose of $scope.apply() is to understand that basically it does an internal .digest() within angular to make sure the scope is in sync (double check if anything has changed, etc).
Most of the time, you never need it! Most typical angular things you'll do, ng-click for example, will automatically trigger it for you when you make any changes to the scope.
But take as an example a jQuery UI dialogBox.
Let's say you prompt the user something, and you need to update your scope when they push the OK button.
Angular isn't aware of that button, nor does it know when any event is fired on it.
Hence, this is a very common use-case for $scope.apply()
Inside of that OK buttons event, you'd simply do:
$scope.apply(function () {
// Angular is now aware that something might of changed
$scope.changeThisForMe = true;
});
In a nutshell $scope.$apply tells Angular and any watchers that values have been changed and to go back and check if there are any new values. This keeps things within the Angular context regardless of how you made a change, like in a DOM event, jQuery method, etc.

Update controller scope from directive

I am creating reusable UI components with AngularJS directives. I would like to have a controller that contains my business logic with the nested components (directives). I want the directives to be able to manipulate a single property on the controller scope. The directives need to have an isolate scope because I might use the same directive more than once, and each instance needs to be bound to a particular controller scope property.
So far, the only way I can apply changes back to the controller's scope is to call scope.$apply() from the directive. But this breaks when I'm inside of an ng-click callback because of rootScope:inprog (scope operation in progress) errors.
So my question: What is the best way to make my controller aware when a child directive has updated a value on the controller's scope?
I've considered having a function on the controller that the directive could call to make an update, but that seems heavy to me.
Here is my code that breaks on an ng-click callback. Keep in mind that I don't just want to solve the ng-click issue. I want the best overall solution to apply reusable directives to modify a parent scope/model.
html
<div ng-controller="myCtrl">
<my-directive value="val1"></my-directive>
</div>
controller
...
.controller('myCtrl', ['$scope', function ($scope) {
$scope.val1 = 'something';
}});
directive
...
.directive('myDirective', [function () {
return {
link: function(scope) {
scope.buttonClick = function () {
var val = 'new value';
scope.value = val;
scope.$apply();
};
},
scope: {
value: '='
},
template: '<button ng-click="buttonClick()"></button>'
};
}]);
The purpose of two-way data binding in directives is exactly what you're asking about -- to "[allow] directives to modify a parent scope/model."
First, double-check that you have set up two-way data binding correctly on the directive attribute which exposes the variable you want to share between scopes. In the controller, you can use $watch to detect updates if you need to do something when the value changes. In addition, you have the option of adding an event-handler attribute to the directive. This allows the directive to call a function when something happens. Here's an example:
<div ng-controller="myCtrl">
<my-directive value="val1" on-val-change="myFunc"> <!-- Added on-change binding -->
<button ng-click="buttonClick()"></button>
</my-directive>
</div>
I think your question about $scope.apply is a red herring. I'm not sure what problem it was solving for you as you evolved this demo and question, but that's not what it's for, and FWIW your example works for me without it.
You're not supposed to have to worry about this issue ("make controller aware ... that [something] modified a value on a scope"); Angular's data binding takes care of that automatically.
It is a little complicated here because with the directive, there are multiple scopes to worry about. The outer scope belongs to the <div ng-controller=myCtrl>, and that scope has a .val property, and there's an inner scope created by the <my-directive> which also has a .val property, and the buttonClick handler inside myDirective modifies the inner one. But you declared myDirective's scope with value: '=' which sets up bidirectional syncing of that property value between the inner and outer scope.
So it should work automatically, and in the plunker I created from your question code, it does work automatically.
So where does scope.$apply come in? It's explicitly for triggering a digest cycle when Angular doesn't know it needs to. (And if you use it when Angular did know it needed a digest cycle already, you get a nested digest cycle and the "inprog" error you noticed.) Here's the doc link, from which I quote "$apply() is used to execute an expression in angular from outside of the angular framework". You need to use it, for example, when responding to an event handler set up with non-Angular methods -- direct DOM event bindings, jQuery, socket.io, etc. If you're using these mechanisms in an Angular app it's often best to wrap them in a directive or service that handles the Angular-to-non-Angular interface so the rest of your app doesn't have to worry about it.
(scope.$apply is actually a wrapper around scope.$digest that also manages exception handling. This isn't very clear from the docs. I find it easier to understand the name/behavior of $digest, and then consider $apply to be "the friendlier version of $digest that I'm actually supposed to use".)
One final note on $apply; it takes a function callback argument and you're supposed to do the work inside this callback. If you do some work and then call $apply with no arguments afterwards, it works, but at that point it's the same as $digest. So if you did need to use $apply here, it should look more like:
scope.buttonClick = function() {
scope.$apply(function() {
scope.value = newValue;
});
});

$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.

Resources