AngularJS $watch not working properly - angularjs

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>

Related

Ng-style function called more than once

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>

When does "$viewValue" become "NOT undefined"?

Here is my example to count value length in the input text:
let app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.submit = function ($event, form) {
$event.preventDefault();
alert(form.myinput.$viewValue.length)
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form id="myForm" ng-model="myForm" name="myForm" novalidate ng-app="myApp" ng-controller="myController">
<input ng-model="myinput" name="myinput" />
Submit
</form>
The problem: If the value in the input is null or empty (the input contained nothing before), it would throw this error when clicking on submit:
TypeError: Cannot read property 'length' of undefined
at a.$$childScopeClass.$$childScopeClass.$scope.submit
Then, I've tried to type something in the input, delete it and click on submit again. It should work.
My question: for input[type=text], is there nothing like default value with property $viewValue?
I mean: if the value is null or empty, form.myinput.$viewValue should be ''. So, the length must be 0.
Try this :
It will check first for null and empty value of the text box and then perform operation according to that.
var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.submit = function () {
if($scope.myinput != null && $scope.myinput.length > 0) {
alert($scope.myinput.length);
} else {
alert("Please enter the text");
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form id="myForm" name="myForm" novalidate ng-app="myApp" ng-controller="myController">
<input ng-model="myinput" name="myinput" />
<button ng-click="submit()">Submit</button>
</form>
You need to access it via the scope. $scope.form.myinput.$viewValue.length
That being said I do not believe that controllers should know about form as forms are a view concept. Anything to do with the form variable should not make their way into your controllers. I am a big fan of not passing the $scope into your controllers at all and using the controller as syntax.
Here is how I would do it. This will only work with Angular 1.3 or greater as 1.2 doesn't support controller as.
let app = angular.module('myApp', []);
app.controller('myController', function () {
var vm = this;
vm.submit = function () {
alert(vm.myinput);
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<form id="myForm" name="myForm" novalidate ng-app="myApp" ng-controller="myController as vm">
<input ng-model="vm.myinput" name="myinput" />
<button ng-click="vm.submit()">Submit</button>
</form>

angularjs manual bootstrapping does not update property value when input changes through method call

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>

Variable in one controller is being affected by the same name variable in another controller

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)

formData not changing with ngChange hook

I have a very basic scenario where in which I have a module with one controller:
var myModule = angular.module('myModule', []);
myModule.controller('myModuleCtrl', function ($scope, $http) {
$scope.formData = {url:'',title:'',source:''};
$scope.init = function() {
$scope.formData.url = 'Test';
$scope.formData.title = '';
$scope.formData.source = '';
};
$scope.manageUrl = function() {
alert('update');
};
});
In my view I'm trying to hook the formData object properties to some form fields using ngModel. However my input doesn't update it's value after the init() method runs. If I add the ngChange directive and hook that up with the $scope.manageUrl() method, it only runs once, after my first keystroke/change of the input.
Am I missing something here? I've used both directives before without any problems. Only thing I can think of is something wrong with my module/controller setup?
This is what my view looks like:
<div ng-app="myApp" ng-controller="myModuleCtrl" ng-init="init()">
<div>
<form name="myForm">
<div>
<input type="url" ng-model="formData.url" ng-change="manageUrl()" />
</div>
</form>
</div>
</div>
And my application.js bootstrapper:
var app = angular.module('myApp', ['myModule']);
It happens because of the url validator, note how the whole url property is removed until you enter a valid url, that's when you'll start to get your alerts back. Basically, once removed, the url is never considered to be changed until a valid (and different) url is input.
url is set to 'Test'
you type anything into the box, it fails the validation and becomes undefined
it stays undefined until you enter a valid url (it's not changing)
Start typing http://anything and see what happens yourself:
var myModule = angular.module('myModule', []);
myModule.controller('myModuleCtrl', function ($scope, $http) {
$scope.formData = {url:'',title:'',source:''};
$scope.init = function() {
$scope.formData.url = 'Test';
$scope.formData.title = '';
$scope.formData.source = '';
};
$scope.manageUrl = function() {
alert('update');
};
});
var app = angular.module('myApp', ['myModule']);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myModuleCtrl" ng-init="init()">
<div>
<form name="myForm">
<div>
<input type="url" ng-model="formData.url" ng-change="manageUrl()" />
<br>
{{formData}}
</div>
</form>
</div>
</div>
Am I missing something here? I've used both directives before without any problems. Only thing I can think of is something wrong with my module/controller setup?
The only logical solution that comes to my mind is that you've used a different input type before, so you were not a subject to validations. If you change the type to text, it works fine the whole time.

Resources