In this plunk I have a div with a border width that is determined by the value in an input field. I achieve that with ng-style containing a getBorder() function.
My issue is that getBorder() is called twice and sometimes three times, instead of once. Why does this happen and how to fix it?
HTML
Width: <input type="number" ng-model="borderWidth"/>
<br/>
<div style="background-color:orange;height:200px;width:100px"
ng-style="{ 'border': getBorder() }"></div>
JavaScript
var app = angular.module('app', []);
app.controller('ctl', function ($scope) {
$scope.getBorder = function(){
alert('getBorder called');
return $scope.borderWidth + 'px solid black';
};
});
This is because of the digest cycles in AngularJS.
AngularJS registers watchers to observe changes in the scope, and as soon as a change happens, it refreshes the bindings between corresponding views/models using digest cycles. This is the reason why you can see live changes in the data and on the screen.
ngModel is one of the directives which registers a watcher. So, the problem you came across, is not really a problem, because ng-style is trying to get the value using getBorder().
I hope that this solution solved your problem
var app = angular.module('app', []);
app.controller('ctl', function ($scope) {
$scope.borderWidth = 1;
$scope.$watch('borderWidth', function (newVal, oldVal) {
console.log()
if (angular.isDefined(newVal)) {
$scope.styleBorder = newVal + 'px solid black';
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<html ng-app="app">
<body>
<div ng-controller="ctl">
Width: <input type="number" ng-model="borderWidth"/>
<br/>
<div
style="background-color:orange;height:200px;width:100px;"
ng-style='{"border": styleBorder}'
></div>
</div>
</body>
</html>
Related
Recently I am learning Angularjs, my code seems not work as expected:
this is my div:
<div ng-app="myApp">
<div ng-controller="myController">
<input type="text" ng-model="data.name" value=""/>
{{data.count}}
</div>
</div>
my controller is:
<script>
var app = angular.module('myApp',[]);
app.controller('myController', function($scope) {
$scope.data = {
name:"tom",
count = 0
}
$scope.$watch('data', function(oldValue,newValue) {
++$scope.data.count;
},true);
})
</script>
what I expect is when I type something in the <input> box, the {{data.count}} will increase by 1 each time. However the code is initially 11 and each time I make changes in the input field, the count is increased by 11, can someone help me find where have I done wrong? Thanks a lot in advance.
Why this happen?
Watcher calls multiple times because you created watcher for full object data. Flag true will create sub-watcher for every value in object.
Its a proper behavior. I believe you want something like:
$scope.$watch('data', function(oldValue,newValue) {
if(oldValue.name != newValue.name){
++$scope.data.count;
}
},true);
Demo Fiddle
The second solution is to watch on name only:
$scope.$watch(function(){
return $scope.data.name
}, function(oldValue,newValue) {
++$scope.data.count;
});
Here is a another way to do it. Use the ng-keydown directive and update the count only when a key is pressed inside the input element.
var app = angular.module('myApp', []);
app.controller('MyController', function MyController($scope) {
$scope.data = {
name: "tom",
count: 0
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller='MyController' ng-app='myApp'>
<input type="text" ng-model="data.name" value="" ng-keydown="data.count = data.count+1" /> {{data.count}}
</div>
I cannot figure out why I am getting an infinite $digest loop error in this simple demo. I've read about these loops in the official documentation, but I don't understand why this demo triggers the error.
CodePen Demo
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.min.js"></script>
<script>
var TestApp = angular.module("TestApp", []);
TestApp.controller("TestCtrl", function ($scope) {
$scope.Counter = 0;
$scope.IncrementCounter = function () {
$scope.Counter+=1;
return true
}
});
</script>
<body ng-app='TestApp'>
<div ng-controller='TestCtrl'>
<label>
Number of times <code>$scope.IncrementCounter()</code>
has been invoked: {{ Counter }}
</label>
<input type="hidden" value="{{ IncrementCounter() === true }}" />
</div>
</body>
Is there no way to increment a $scope variable from within a $scope function without causing the whole model to go through a digest cycle?
The problem is because you were calling the IncrementCounter() function from within a binding in the view. In a $digest cycle Angular looks at the {{ }} brackets and executes any functions held within them, your IncrementCounter() function happened to change a value on the scope $scope.Counter, and this in turn kicks off another $digest cycle, and so the process repeats continually.
You should do all of this in the Controller and only use the view for displaying values held on the scope. You could for example do this using a function that calls itself, optionally using the $timeout service to create a delay:
var TestApp = angular.module("TestApp", []);
TestApp.controller("TestCtrl", function ($scope, $timeout) {
var init = function() {
$scope.counter = 0;
incrementCounter();
};
var incrementCounter = function () {
$scope.counter++;
$timeout(function() {
incrementCounter();
}, 1000);
};
init();
});
With the above code your view can then be:
<html>
<head>
<title>AngularJS Function Invocation Tester</title>
</head>
<body ng-app='TestApp'>
<h2>AngularJS Function Invocation Tester</h2>
<div ng-controller='TestCtrl'>
<label>
Number of times <code>$scope.IncrementCounter()</code>
has been invoked: <span class="counter">{{ counter }}</span>
</label>
</div>
</body>
</html>
The problem, I have is AngularJS application is not updating the result when input changes in the input HTML field. If I turn this to auto bootstrapping it does work as expected. I do not know what am i doing wrong?
This is JS file.
angular.module('doublevalue', [])
.controller('DoubleController', ['$scope', function($scope){
$scope.value = 0;
$scope.double = function(value) { $scope.value = value * 2; }
}]);
angular.element(document).ready(function() {
var div3 = document.getElementById('App3');
angular.bootstrap(div3, ['doublevalue']);
});
JSFIDDLE version:
https://jsfiddle.net/as0nyre3/48/
HTML file:
<div id ='App3' ng-controller='DoubleController'>
Two controller equals
<input ng-model='num' ng-change='double(num)'>
<span> {{ value }}</span> </div>
Auto bootstrapping one link:
https://jsfiddle.net/as0nyre3/40/
Please help me!
This is working, with a problem like that you should always check your selectors
<div id="App3" ng-controller="myCtrl">
<input type='text' ng-model='name' ng-change='change()'>
<br/> <span>changed {{counter}} times </span>
</div>
<script>
angular.element(document).ready(function() {
angular.bootstrap(document.getElementById('App3'), ['myApp']);
})
</script>
I'm in a project that uses angularjs and rails. So, i'm using this library too:
https://github.com/FineLinePrototyping/angularjs-rails-resource
Well, when i'm using the controller as syntax from angularjs, some strange behaviour is happening. You can see that in this plunker example:
http://plnkr.co/edit/i4Ohhh8llS7WN68sLX5q?p=preview
The promise object returned by the remote call in first controller using the angularjs-rails-resource library in some way is setting the instance variable that belongs to the second controller. I don't know if it is a bug in the library, or an angular behaviour that i should know. Anyway, is clearly an undesirable behaviour.
Here is the same plunker code (index.html):
<!doctype html>
<html ng-app="Demo">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.13/angular.js"></script>
<script src="https://rawgit.com/FineLinePrototyping/dist-angularjs-rails-resource/master/angularjs-rails-resource.min.js"></script>
<script src="example.js"></script>
</head>
<body>
<div ng-controller="Controller1 as ctrl1">
<form>
<label>should appear first remote</label>
<input type="text" ng-model="ctrl1.remote.name"/><br>
<label>should appear first local</label>
<input type="text" ng-model="ctrl1.local.name"/>
</form>
</div>
<br>
<div ng-controller="Controller2 as ctrl2">
<form>
<label>should appear second local</label>
<input type="text" ng-model="ctrl2.remote.name"/><br>
<label>should appear second local</label>
<input type="text" ng-model="ctrl2.local.name"/>
</form>
</div>
</body>
</html>
My angularjs code (example.js):
angular.module('Demo', ['rails']);
angular.module('Demo').controller('Controller1', ['$scope', 'Remote', function($scope, Remote) {
ctrl = this;
ctrl.remote = {};
Remote.get().then(function(remote) {
ctrl.remote = remote;
});
ctrl.local = {};
ctrl.local.name = "first local";
}]);
angular.module('Demo').controller('Controller2', ['$scope', function($scope) {
ctrl = this;
// SAME VARIABLE NAME
// WILL RECEIVE VALUE FROM REMOTE CALL ON FIRST CONTROLLER!!!
ctrl.remote = {};
ctrl.remote.name = "second local";
// SAME VARIABLE NAME
ctrl.local = {};
ctrl.local.name = "second local";
}])
angular.module('Demo').factory('Remote', [
'railsResourceFactory',
'railsSerializer',
function (railsResourceFactory, railsSerializer) {
return railsResourceFactory({
url:'clients.json',
name: 'remote',
})
}]
);
clients.json
{
"name":"first remote"
}
Any ideias how fix this without having to change variable names to avoid conflict? Because that way we will just mask the problem.
I report the problem to angularjs-rails-resource library but no answer until now.
You need to use var when declaring your variables, otherwise they're global.
Use
var ctrl = this; instead of just ctrl = this;
Also, 'use strict' is a nice thing to use(and it helps in these situations)
Say I have some html as follows:
<html>
<head> angular etc. </head>
<body ng-app>
<div ng-controller="MyCtrl">
<input ng-model="weight" type="number" min="{{minWeight}}" max="{{maxWeight}}">
<p>{{weight}}</p>
</div>
</body>
</html>
and the following controller:
function MyCtrl($scope){
$scope.weight = 200;
$scope.minWeight = 100.0;
$scope.maxWeight = 300.0;
}
The "min" and "max" will show the user their input is bad, and if I hard code them to say, 100 and 300, it will make sure the value is never bound to the model at all (why isn't the behavior the same??). What I'd like to do is only actually change the "weight" if the value meets the input requirements. Any thoughts?
I don't fully understand what are you trying to do.
HTML:
<html>
<head> angular etc. </head>
<body ng-app="MyApp">
<div ng-controller="MyCtrl">
<input ng-model="weight" type="number" min="{{minWeight}}" max="{{maxWeight}}">
<p>{{weight}}</p>
</div>
</body>
</html>
Angular: [Edit]
var app = angular.module('myApp', []);
app.controller('MyCtrl', ['$scope', function MyCtrl($scope){
$scope.weight = 200;
$scope.minWeight = 100.0;
$scope.maxWeight = 300.0;
$scope.$watch('weight', function (newValue, oldValue) {
if(typeof newValue === 'number') {
if(newValue > $scope.maxWeight || newValue < $scope.minWeight) {
$scope.weight = oldValue;
}
}
});
}]);
But here is an example I made in jsFiddle. I hope this was a solution you were looking for.
[Edit]
http://jsfiddle.net/migontech/jfDd2/1/
[Edit 2]
I have made a directive that does delayed validation of your input field.
And if it is incorrect then it sets it back to last correct value.
This is totally basic. You can extend it to say if it is less then allowed use Min value, if it is more then allowed use Max value it is all up to you. You can change directive as you like.
http://jsfiddle.net/migontech/jfDd2/2/
If you have any questions let me know.