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
Related
small question (possible a logic err), I am trying to get the checkbox to update itself automatically after clicking it and in the same time using the rest api to PUT the changes in DB, and the PUT mothod works, it updates the DB but it`s not updating the checkbox state itself only if I refresh the page, the checkbox will update.
And I have this simple code:
<input type="checkbox" ng-checked="action.state" ng-click="setState($event, key, action)"><div class="track"><div class="handle"></div></div>
and back-end as this:
.controller('Actions', function ($scope, $filter, $resource, $ionicActionSheet, $ionicModal) {
var actionListResource = $resource('/api/actions/');
actionListResource.query(function (data) {
$scope.actions = data;
});
$scope.setState = function (event, index, action) {
if (action.widget === 'toggle' && action.state === 1) {
action.state = 0;
}
else {
action.state = 1;
}
event.preventDefault();
var actionsResource = $resource('/api/actions/:actionId/', {actionId:'#id'}, {
'update': {
method: 'PUT'
}
});
};
...more code here
})
action.state is always a 1 or 0 value, I've checked the $scope.actions[index].state and it`s updating itself when I click the checkbox.
Thank you!
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app="myApp">
<div ng-controller="myCtrl">
<input type="checkbox" ng-model="action" ng-click="setState()">
</div>
<script>
angular.module('myApp', [])
.controller('myCtrl', ['$scope', function($scope) {
$scope.setState = function() {
window.alert($scope.action);
// $scope.action value will be true or false
};
}]);
</script>
</body>
</html>
Try ng-model instead of ng-checked. The toggle stuff looks unecessary. ng-change could also be used to trigger the request.
In their docs they solve exactly the problem you describe.
https://docs.angularjs.org/api/ng/directive/ngChange
I have list of objects named rolePermissionList like this:
[{"id":1,"name":"createUser","type":"user","marked":1},{"id":2,"name":"deleteUser","type":"user","marked":1},{"id":3,"name":"editRole","type":"role","marked":0}]
and I use ng-repeat to repeat checkboxes using the values in that list like this
<div class="form-group">
<label>Role Permissions:</label>
<div class="checkbox" ng-repeat="permission in rolePermissionList">
<label>
<input type="checkbox" ng-model="idsPermission[permission .idPermission ]"
ng-checked="permission.checked">{{permission.name}}
</label>
</div>
</div>
the ng-model of the checkboxes is named idsPermission and it's a list of numbers, those numbers are the IDS of the objects.
When I load the page the checkboxes that are supposed to be checked are checked this part works fine, but when I check another checkbox all the checkboxes gets checked, and when I uncheck a checkbox the same thing happens all the checkboxes gets unchecked.
I use that list of numbers named idsPermission to get all the IDS of the checkboxes that are checked, this worked before I used the directive ng-checked="permission.checked", but now I need to use it since now I need to show the checkboxes that are already marked.
this is my controller
angular.module('MyApp')
.controller('RolCtrl', ['$scope', 'RolService',
function ($scope, RolService) {
$scope.idsPermission = {};
$scope.getListCheckBoxesEditRole = function (idRole) {
$scope.selectRol.descripcion;
RolService.getListCheckBoxesEditRole(idRole)
.then(
function (d) {
var userPermissionList = [];
for (var permission in d) {
if (d[permission ].type === 'user') {
if (d[permission ].marked === 1)
{
d[permission ].checked = true;
userPermissionList.push(d[permission ]);
} else {
userPermissionList.push(d[permission ]);
}
}
}
$scope.rolePermissionList = userPermissionList;
},
function (errResponse) {
console.error('ERROR');
}
);
};
}
$scope.getListCheckBoxesEditRole(3);
]);
The RolService.getListCheckBoxesEditRole(idRole) service returns this JSON [{"id":1,"name":"createUser","type":"user","marked":1},{"id":2,"name":"deleteUser","type":"user","marked":1},{"id":3,"name":"editRole","type":"role","marked":0}]
and what I do in the controller is iterate over that list and check if the marked field is 1 if it's 1 I do this d[permission ].checked = true; I what I think that I do in that line is setting the checked value to true so I could use this directive in the html view ng-checked="permission.checked"
I tried doing this ng-checked="idsPermission[permission.checked]" but when I do this the values that are marked=1 in the JSON that I paste above don't appear checked when I load the page, but if I put it like this ng-checked="permission.checked" they appear marked as they should, but when I click a checkbox all the checkboxes gets selected.
I came across too many issues to document but the main problem was how you are iterating through the array that is returned from the service. It should be something like this:
Controller
angular.forEach(d.data, function(permission) {
if (permission.type === 'user') {
if (permission.marked === 1) {
permission.checked = true;
userPermissionList.push(permission);
} else {
userPermissionList.push(permission);
}
}
});
Then you can simplify your html like this:
HTML
<input type="checkbox" ng-model="permission.checked" />
You can see all of the changes in this working plunk.
I can't see in your code that $scope.idsPermission is getting defined. In ng-repeat you only set the key for the object but the value is undefined. That's why the checkbox won't show the correct value.
You could use ng-init to initialize the model. Please have a look at the simplified demo below or this fiddle.
(Also defining the models in your controller would be possible.)
Only using ng-model should be enough for the checkbox to work. I think I've read somewhere that ng-checked and ng-model aren't working smoothly together.
angular.module('demoApp', [])
.controller('mainCtrl', MainCtrl);
function MainCtrl() {
var vm = this;
angular.extend(vm, {
data: [{
id: 0,
marked: 1
}, {
id: 1,
marked: 0
}, {
id: 2,
marked: 1
}]
})
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="mainCtrl as ctrl">
<div ng-repeat="item in ctrl.data">
<input type="checkbox" ng-init="ctrl.idPermissions[item.id] = !!item.marked" ng-model="ctrl.idPermissions[item.id]"/>{{item.id}}
</div>
<pre>
permissions: {{ctrl.idPermissions | json: 2}}
data{{ctrl.data | json: 2}}</pre>
</div>
I've created a small sample of what is happening.
http://plnkr.co/edit/py9T0g2aGhTXFnjvlCLF
Basically, the HTML is:
<div data-ng-app="app" data-ng-controller="main">
<select class="ui dropdown" id="ddlState" data-ng-options="s.name for s in states track by s.id" data-ng-model="selectedState"></select>
<select class="ui dropdown" id="ddlCity" data-ng-options="c.name for c in cities track by c.id" data-ng-model="selectedCity"></select>
</div>
And the javascript is:
angular.module("app", [])
.controller("main", function($scope, $timeout) {
$scope.selectedState = {id:1,name:"A"};
$scope.selectedCity = {id:1,name:"A.1",stateId:1};
$scope.states = [{id:1,name:"A"},{id:2,name:"B"},{id:3,name:"C"}];
var fakeDataSource = [
{id:1,name:"A.1",stateId:1},
{id:2,name:"A.2",stateId:1},
{id:3,name:"A.3",stateId:1},
{id:4,name:"B.1",stateId:2},
{id:5,name:"B.2",stateId:2},
{id:6,name:"B.3",stateId:2},
{id:7,name:"C.1",stateId:3},
{id:8,name:"C.2",stateId:3},
{id:9,name:"C.3",stateId:3}
];
$scope.$watch("selectedState", function(n,o){
if (n !== o)
$scope.selectedCity = null;
$scope.cities = fakeDataSource.filter(function(x){
return n.id === x.stateId;
});
$timeout(function(){
$(".ui.dropdown").dropdown().dropdown("refresh");
});
})
$timeout(function(){
$(".ui.dropdown").dropdown();
})
})
The problem is when I change the first dropdown to value 'B' or 'C', the value of second dropdown does not change, even it is changed in angular model.
You guys can notice that I've the line $(".ui.dropdown").dropdown().dropdown("refresh") to refresh the values but does not work.
I tried destroy and recreate using $(".ui.dropdown").dropdown("destroy").dropdown() but still does not work.
Any help?
Simply using ngModel won't make the values change dynamically. Take a look at the documentation here: https://docs.angularjs.org/api/ng/directive/ngModel
You can bind the values using ngBind or what I have done is do an onChange to then check the value and change your second drop down accordingly. Something like:
$("#ddlState").on("change", function(e) {
//check $scope.selectedState for it's value, and change #ddlCity/$scope.selectedCity accordingly
});
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
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;
}
};
});