Angular ng-class not updating after ng-change is fired - javascript

EDIT: It seems my issue is more complex than the simple typo in the code below. I have 3rd party components interacting and raising change events on the inputs which angular is picking up when I don't want it to. The problem is somewhere in there. I will try to find a simple fiddle and update the question if I manage it.
I have a pair of inputs, which have an ng-model and share an ng-change function. The ng-change sets a boolean value in the controller which is supposed to update the class(es) on the inputs through an ng-class directive. However the first of the two inputs never seems to get any updates to it's class. Here is a simplified version:
View:
<div ng-controller='TestCtrl'>
<input type="text" ng-class="{ 'invalid': firstInvalid }" ng-model="firstValue" ng-change="doOnChange()"></input>
<input type="text" ng-class="{ 'invalid': secondInvalid }" ng-model="secondValue" ng-change="doOnChange()"></input>
</div>
Controller:
function TestCtrl($scope) {
$scope.firstInvalid = false;
$scope.secondInvalid = false;
$scope.firstValue = '';
$scope.secondValue = '';
$scope.doOnChange = function () {
console.log('change fired');
$scope.firstInValid = !$scope.firstInvalid;
$scope.secondInvalid = !$scope.secondInvalid;
};
};
Codepen:
http://codepen.io/Samih/pen/ZGXQPJ
Notice how typing in either input, the second input updates with the class just as I would expect, however the first never gets the 'invalid' class.
Thanks in advance for your help.

Check your code for typos:
This line
$scope.firstInValid = !$scope.firstInvalid;
should be
$scope.firstInvalid = !$scope.firstInvalid;
It should be $scope.firstInvalid, not $scope.firstInValid.

It seems to work for me :
Just edited 'firstInvalid'.
View:
<div ng-controller='TestCtrl'>
<input type="text" ng-class="{ 'invalid': firstInvalid }" ng-model="firstValue" ng-change="doOnChange()"></input>
<input type="text" ng-class="{ 'invalid': secondInvalid }" ng-model="secondValue" ng-change="doOnChange()"></input>
</div>
Controller:
function TestCtrl($scope) {
$scope.firstInvalid = false;
$scope.secondInvalid = false;
$scope.firstValue = '';
$scope.secondValue = '';
$scope.doOnChange = function () {
console.log('change fired');
$scope.firstInvalid = !$scope.firstInvalid;
$scope.secondInvalid = !$scope.secondInvalid;
};
};
Codepen: http://codepen.io/vikashverma/pen/BNwKmQ

Related

How to fix ng-if not reacting?

I'm trying to display a link <a href> when a condition is met, but the link never appears.
I have already tried to change the position (in/out of <div controller>) and the tag of the item containing the ng-if (span, div, ul...). This kind of code works on another HTML I have, so my Angular version seems okay.
Here is the form where I call the controller:
<div ng-controller="userRole">
<form ng-submit="setProfile()">
<select ng-model="userRole"
ng-options="role for role in roles">Role</select>
<input type="text"
ng-model="userName"
placeholder="Nom"></input>
<input type="submit"
value="Valider"></input>
</form>
</div>
The condition right after:
<span ng-if="user.isSetup">
Accès aux cours
</span>
And the actual controller:
var app = angular.module('srsApp', []);
app.controller('userRole', function($scope) {
$scope.roles = ['Professeur', 'Élève'];
$scope.user = {role:'', name:'', isSetup: false};
$scope.setProfile = function() {
if ($scope.userName !== '' && $scope.userRole !== '') {
$scope.user.role = $scope.userRole;
$scope.user.role = $scope.userName;
$scope.user.isSetup = true;
$scope.userRole = '';
$scope.userName = '';
}
};
});
</script>
I expected the link to appear once I submit the form with a role and a name, but the link stays hidden. No error from my Firefox terminal, and I know it enters the function because the placeholders are re-initialized.
As mentionned by #AnuragSrivastava, the <span> should be in the <div ng-controller="userRole">. As I said I tried it, but previous circumstances (syntax errors NOT highlighted by terminal) prevented this to work.

How to pass focus to a new field as soon as it appears (angular 1.4)?

In the following example a new field is added (by adding a blank row to $scope) when the last field loses focus if it is not empty. The problem is that the new field is not added to the DOM in time to receive focus.
Is there a way to detect when angular has finished appending new field to the DOM and then pass focus to it?
Please, no "timer" solutions; the time it takes to change DOM is unknown and I need this focus switch to happen as fast as possible. We can do better!
JSFiddle
HTML
<div ng-app='a' ng-controller='b'>
<input type="text" ng-repeat="row in rows" ng-model="row.word" ng-model-options="{'updateOn': 'blur'}">
</div>
JS
angular.module('a', []).controller('b', function ($scope) {
$scope.rows = [{'word': ''}];
$scope.$watch('rows', function (n, o) {
var last = $scope.rows[$scope.rows.length - 1];
last.word && $scope.rows.push({'word': ''});
}, true);
});
This is a View-concern and so should be dealt with by using directives.
One way to do so, is to create a directive that grabs the focus when it's linked:
.directive("focus", function(){
return {
link: function(scope, element){
element[0].focus();
}
}
});
and use it like so:
<input type="text"
ng-repeat="row in rows"
ng-model="row.word"
focus>
Demo
Use $timeout without specifying a number of milliseconds. It will, by default, run after the DOM loads, as mentioned in the answer to this question.
angular.module('a', []).controller('b', function($scope, $timeout) {
$scope.rows = [{
'word': ''
}];
$scope.addRow = function() {
$scope.rows.push({
'word': ''
});
$timeout(function() {
//DOM has finished rendering
var inputs = document.querySelectorAll('input[type="text"]');
inputs[inputs.length - 1].focus();
});
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='a' ng-controller='b'>
<div ng-repeat="row in rows">
<input type="text" ng-model="row.word" ng-model-options="{'updateOn': 'blur'}"><br>
</div>
<input type="button" ng-click="addRow()" value="Add Row">
</div>

watchcollection swallowing an attribute being watched

There are two attributes selCountry and searchText. There is a watch that monitors these two variables. The 1st one is bound to a select element, other is a input text field.
The behavior I expect is: If I change the dropdown value, textbox should clear out, and vice versa. However, due to the way I have written the watch, the first ever key press (post interacting with select element) swallows the keypress.
There must be some angular way of telling angular not to process the variable changes happening to those variables; yet still allow their changes to propagate to the view...?
$scope.$watchCollection('[selCountry, searchText]', function(newValues, oldValues, scope){
console.log(newValues, oldValues, scope.selCountry, scope.searchText);
var newVal;
if(newValues[0] !== oldValues[0]) {
console.log('1');
newVal = newValues[0];
scope.searchText = '';
}
else if(newValues[1] !== oldValues[1]) {
console.log('2');
newVal = newValues[1];
scope.selCountry = '';
}
$scope.search = newVal;
var count = 0;
if(records)
records.forEach(function(o){
if(o.Country.toLowerCase().indexOf(newVal.toLowerCase())) count++;
});
$scope.matches = count;
});
Plunk
I think the problem you are encountering is that you capture a watch event correctly, but when you change the value of the second variable, it is also captured by the watchCollection handler and clears out that value as well. For instance:
selCountry = 'Mexico'
You then change
selText = 'City'
The code captures the selText change as you'd expect. It continues to clear out selCountry. But since you change the value of selCountry on the scope object, doing that also invokes watchCollection which then says "okay I need to now clear out searchText".
You should be able to fix this by capturing changes using onChange event handlers using ng-change directive. Try the following
// Comment out/remove current watchCollection handler.
// Add the following in JS file
$scope.searchTextChange = function(){
$scope.selCountry = '';
$scope.search = $scope.searchText;
search($scope.search);
};
$scope.selectCountryChange = function(){
$scope.searchText = '';
$scope.search = $scope.selCountry;
search($scope.search);
};
function search(value){
var count = 0;
if(records)
records.forEach(function(o){
if(o.Country.toLowerCase().indexOf(value.toLowerCase())) count++;
});
$scope.matches = count;
}
And in your HTML file
<!-- Add ng-change to each element as I have below -->
<select ng-options="country for country in countries" ng-model="selCountry" ng-change="selectCountryChange()">
<option value="">--select--</option>
</select>
<input type="text" ng-model="searchText" ng-change="searchTextChange()"/>
New plunker: http://plnkr.co/edit/xCWxSM3RxsfZiQBY76L6?p=preview
I think you are pushing it too hard, so to speak. You'd do just fine with less complexity and watches.
I'd suggest you utilize some 3rd party library such as lodash the make array/object manipulation easier. Try this plunker http://plnkr.co/edit/YcYh8M, I think it does what you are looking for.
It'll clear the search text every time country item is selected but also filters the options automatically to match the search text when something is typed in.
HTML template
<div ng-controller="MainCtrl">
<select ng-options="country for country in countries"
ng-model="selected"
ng-change="search = null; searched();">
<option value="">--select--</option>
</select>
<input type="text"
placeholder="search here"
ng-model="search"
ng-change="selected = null; searched();">
<br>
<p>
searched: {{ search || 'null' }},
matches : {{ search ? countries.length : 'null' }}
</p>
</div>
JavaScript
angular.module('myapp',[])
.controller('MainCtrl', function($scope, $http) {
$http.get('http://www.w3schools.com/angular/customers.php').then(function(response){
$scope.allCountries = _.uniq(_.pluck(_.sortBy(response.data.records, 'Country'), 'Country'));
$scope.countries = $scope.allCountries;
});
$scope.searched = function() {
$scope.countries = $scope.allCountries;
if ($scope.search) {
var result = _.filter($scope.countries, function(country) {
return country.toLowerCase().indexOf($scope.search.toLowerCase()) != -1;
});
$scope.countries = result;
}
};
});

AngularJS - stop ng-click expression evaluation after $event.stopPropagation

I'm struggling stoping ng-click from evaluating the rest of expression after event cancelation was called.
I've made the following plnkr.
If you click on "Click me" text, you'll see that the value is changing, although the clickMe function is calling all known methods (by me, at least) to stop the event.
Markup:
<body ng-controller="Home" ng-init="model = { value: false }">
<div ng-click="clickMe($event); model.value = true;">Click me!!</div>
{{ model.value }}
</body>
App:
APP.controller('Home', function($scope){
$scope.clickMe = function($event){
$event.stopPropagation();
$event.preventDefault();
return false;
}
})
I know that I can pass arguments to clickMe function and if "isOK" is false, then update what I need, but for many reasons bound to my application, I try not to do that.
Update
In my app I had something like this on ng-click
model.PS.title = value; model.PS.desc = value2; model.PS.isSce = value3
I was blind and I couldn't see that I can make it a lot simpler like this:
model.PS = { title: value, desc: value2, isSce: value3 }
... and in this case i can use the shorthand expression for if statement like #Emmanuel mentioned.
I was thinking that ngClick evaluates the expressions inside it like a function block, so if it meets a return false (or something similar) it stops there.
You could evaluate your expression after you have run your click handler with $scope.$eval.
You can pass your expression that you want to run after the click is valid to your click handler and evaluate the expression depending on your isOK variable.
I've extended the demo a bit to show the functionality of $eval.
Please find the updated plunkr here.
HTML
<body ng-controller="Home" ng-init="model = { value: false }; model2 = {value:true}">
<div ng-click="clickMe($event, 'model.value = true; model2.value = false;');">Click me!!</div>
{{ model.value }}
{{ model2.value }}
<br/>
check to enable click me!
<input type="checkbox" ng-model='isOK'/>
</body>
JS
var APP = angular.module('myApp', []);
APP.controller('Home', function($scope){
//var isOk = true;
$scope.isOK = false;
$scope.clickMe = function($event, args){
if($scope.isOK){
console.log(args);
$scope.$eval(args);
return false;
} else {
console.log('isOk false:', args);
}
}
});
How about
<body ng-controller="Home" ng-init="model = { value: false }">
<div ng-click="model.value = clickMe($event) ? true : model.value;">Click me!!</div>
{{ model.value }}
</body>
Leave App as is
APP.controller('Home', function($scope){
$scope.clickMe = function($event){
$event.stopPropagation();
$event.preventDefault();
return false;
}
})

angularjs dynamically change model binding

I have 2 forms - a billing and a shipping. If the user checks a checkbox, the shipping address should be populated with the billing address and turn disabled. If the user unchecks the box, the shipping address should be blank and return to enabled.
I have this working right now with $watch but it feels hacky. I have 2 $watches nested in eachother watching the same element. I want to know if there is a better way to achieve what I am doing.
I tried using a ternary operator in the ng-model like below but that didn't work either.
<input ng-model="isSameAsBilling ? billName : shipName" ng-disabled="isSameAsBilling" />
A plunkr of my "working" code
HTML:
<input ng-model="billName" />
<input type="checkbox" ng-checked="isSameAsBilling" ng-click="sameAsBillingClicked()"/>
<input ng-model="shipName" ng-disabled="isSameAsBilling" />
JavaScript:
$scope.isSameAsBilling = false;
$scope.sameAsBillingClicked = function(){
$scope.isSameAsBilling = !$scope.isSameAsBilling;
};
$scope.$watch('isSameAsBilling', function(isSame){
if ($scope.isSameAsBilling) {
var shipNameWatcher = $scope.$watch('billName', function (newShipName) {
$scope.shipName = $scope.billName;
var secondBillWatcher = $scope.$watch('isSameAsBilling', function(isChecked){
if (!isChecked){
shipNameWatcher();
secondBillWatcher();
$scope.shipName = '';
}
});
});
}
});
I think I've finally got what you're after here.
When the checkbox is checked, it registers a $watch on the billName and mirrors it to the shipName.
When the checkbox is unchecked, the deregisters the $watch and clears the shipName
angular
.module('app', [])
.controller('appController', ['$scope', function($scope){
$scope.isSameAsBilling = false;
$scope.isSameChanged = function() {
if ($scope.isSameAsBilling) {
// register the watcher when checked
$scope.nameWatcher = $scope.$watch('billName', function(bName) {
$scope.shipName = bName
})
} else {
// deregister the watcher and clear the shipName when unchecked
$scope.nameWatcher();
$scope.shipName = ''
}
}
}]);
and here is the PLUNK

Categories

Resources