I am new to AngularJS and I follow a long with some tutorial to get started. In one tutorial it shows how to remove watcher with the example below:
$scope.breakit = $scope.$watch('mydata.val',function(newval){
$scope.mydata.toolong = newval.length >3;
})
<button ng-click="breakit()">Break It</button>
It works, but what I don't understand is why is it calling that breakit function will remove the watcher?
$scope.$watch returns a function that deregisters the watcher when called.
$watch in angularjs returns a deregistration function, calling it would deregister the $watcher this is what explained in example you are going through.
now question is when you would want to unwatch an expression. Its simple when it would be of no use for you (as its consist of performance). e.g like in your code shown above,
Its setting a variable $scope.mydata.toolong = 1 when value is greater than 3 might be for some validation purpose but its also providing you a button "Break it" if you want to enter the value greater than 3 to just break the validation by deregistration function watch.
Related
I'm pretty new to angular js and I have a question about the ng-repeat. I'm following examples at AngularJs : https://docs.angularjs.org/guide/concepts
The section that I'm currently on is 'Adding UI logic: Controllers'. If you look at the index.html, specifically at the code:
<span ng-repeat="c in invoice.currencies">
{{invoice.total(c) | currency:c}}
</span>
When I run in the browser, everything works as expected, but I notice that invoice.total function runs at least 6 times. How do I know? Well in the index.js I added a console.log function within the total function as seen here:
this.total = function total(outCurr){
console.log(outCurr)
console.log(this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr))
return this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr);
};
I would expect the code to run 3 times because the invoice.currencies only has 3 items:
this.currencies = ['USD', 'EUR', 'CNY'];
However, this is what I see in my browser console window:
Any ideas? Thank you in advance?
user2341963 has probably the good answer so i'll quote it first and i'll add some precision.
The digest cycle runs at least twice to check that a value hasn't
changed. First time it runs for each of the values in this.currencies.
It runs again to check that the first digest cycle hasn't changed any
values. It hasn't so it stops after the 2nd cycle
In angular you have what we call a digest cycle. This cycle watch for any change that happened in the application bind to angular. In order to refresh it. This permit to refresh your DOM when you need without refreshing the whole page.
"The bind to angular" means
Every block between {{}}
Every use of scope.$watch or attrs.$observe which are used massively by directives (ng-model/ng-repeat/...).
I don't know what makes this cycle to trigger twice at start this is probably because of some internal mechanics or the filter that you use. Check this link for more info : https://www.ng-book.com/p/The-Digest-Loop-and-apply/
I have a textarea tag with jquery.nicescroll pluggin and ng-model attached to it.
<textarea id="paper" ng-model="paper"></textarea>
In my code I apply a $watch on this ng-model variable.
$scope.$watch("paper", onTextChange);
Everything is good except that onTextChange is fired not only when I type something, but when I click away from textareaб and also when I switch to another tab.
How can I prevent it so that onTextChange is fired only when the text is changed, meaning when I type in something or delete chars?
Demo with instructions: plunker
here's a fix:
http://plnkr.co/edit/kycmUrthYU38Ukdz0jJG?p=preview
setTimeout(
function() {
$scope.$watch("paper", function(newtext, oldtext) {
if (newtext !== oldtext) {
onTextChange();
}
});
}, 100)
So the issue is that watch fires the function whenever angularjs tells the app to digest. What you did was tell it to call the 'change' function EVERY time, when you should have passed in a checker function to check the change happened. It's about 'watching', not about 'watching for changes' - the function argument is supposed to see if you need to do something.
Extra note:
AngularJS sets up watchers for all kinds of things on various elements - here's a bit more info. I believe the blur corresponds to ng-touched which triggers the digest What gets added to $scope.$$watchers by default in Angular? And what triggers $digests?
It is a bad idea to use setTimeout. Instead, you could use ng-model-options="{ getterSetter: true }" and write a method to get/set the value (Modified get/set Plunk) and handle the text change condition within this method.
I have a form where my intent is for required fields to not always be enforced. For example if the user is saving the document as a draft they can enter as little information as they like, if they try and publish the document then they have to enter all the required fields. I'm using a boolean on the controller which changes according to which button has been pressed e.g.
<input type="text" ng-model="field2" ng-required="enforceRequired" />
The problem is that the fields are not re-evaluated when the boolean changes so the form is submitted and then it becomes invalid. Please see this JSFiddle to see what I mean. If you fill in field1 and then click publish it will succeed on the first click and THEN become invalid.
How can I force the validation to run before the form is submitted?
Yarons is right, you are changing the value too late, by late I mean after the form validations has been run. What you can do as a workaround is, after changing the required value, let angular do another cycle and then show your alert. This can be done via $timeout service, although I must mention that it is usually not a good practise to change the flow of your digest actions. It gets pretty messy pretty soon.
Change your publish function like this (and don't forget to inject $timeout)
$scope.publish = function () {
$scope.enforceRequired = true;
$timeout(function () {
if ($scope.form.$valid) {
alert("Published!");
}
});
};
Working fiddle: http://jsfiddle.net/bh9q00Le/14/
The problem is that you are changing the value of enforceRequired in the middle of the digest loop, so the watchers are not re-rendered before you check the input fields' validity (read about digest here).
If you want to get around it, I suggest one of the following methods:
change the value of enforceRequired before you call saveDraft or publish. see example.
call $scope.$apply() after you change the value of enforceRequired. see another example.
I just want to understand why in the following jsFiddle 'here is a lo' is printed three times.
http://jsfiddle.net/wg385a1h/5/
$scope.getLog = function () {
console.log('here is a log');
}
Can someone explain me why ? What should I change to have only one log "here is a log" (that's what I would like this fiddle do). Thanks a lot.
Angular uses digest cycles/iterations to determine when state has changed and needs to update the UI. If it finds any change on one of it's cycles, it keeps rerunning cycles until the data stabilizes itself. If it's done 10 cycles and the data is still changing, you'll see a rather know message: "angularjs 10 iterations reached. aborting".
Therefor, The fact that you are seeing the message displayed 3 times is because you have a simple interface. In fact, you can get up to many more such messages in the log, due to the fact that your directive uses {{getLog()}}. Angular keeps evaluating the expression to see if it changed.
To avoid such problems, under normal circumstances, you should store the value returned by the function you want called only once in the $scope object inside the controller and use that variable (not the function call) in the UI.
So in the controller you'd have $scope.log = getLog() [assuming it returns something, and not just writing to the console] and in the directive use the template {{log}}. This way, you'll get the value only once, per controller instance.
Hope I was clear enough.
I'm using the ocModal directive + service (https://github.com/ocombe/ocModal). When someone wants to delete a record they click the delete button which pops up, via ocModal, a modal to confirm.
oc-modal-close ng-click="deleteNote(id)"
So far so good. Within the controller deleteNote runs. Console.log shows the argument is the id I passed. I call an API to delete the note from my database and on a success call a function to delete the note from the Angular variable $scope.notes.
$scope.removeNote = function(id){
console.log(id);
console.log($scope.notes);
delete $scope.notes[id];
console.log($scope.notes);
};
The id is correct and the $scope.notes after the delete command shows it was correctly deleted. Yet, there is no corresponding update on my front-end. Following advise I've seen on Stackover I used apply() which led to a 'digest cycle already in progress' error. I then tried to use $timeout but while that got rid of the digest cycle error, it didn't solve the problem of the bind.
Is it relevant that the $scope.notes is used by a ng-repeat that then uses a directive? I've experimented a bit and don't think so, but just in case wanted to mention it.
Where are you calling $scope.removeNote from? Is it from your main controller or in the modal's controller? maybe a directive?
It feels like it is hitting the wrong scope. You can try to move notes to $scope.model.notes and see if it helps, as it will ensure the correct scope is referenced, but it's hard to say unless you try and provide a fiddle with the issue occurring.
I've created this simple fiddle trying to mimic an API call with $timeout and it works -> http://jsfiddle.net/7eqsc/1/
angular.module('myApp',[])
.controller('myCtrl',function($scope,$timeout){
$scope.notes={
a:'AngularJS',
b:'Rocks'
}
$scope.addRandom=function(){
$scope.notes[parseInt(Math.random()*10000).toString(36)]='New Item';
}
$scope.removeNote=function(id){
//emulate API call
$timeout(function(){
delete $scope.notes[id];
},2000);
}
});
In general, I recommend placing things under an object, such as ".model.yourArray", it ensures references are kept correctly and will save you a lot of trouble.