Angular way of binding elements of the same name together - angularjs

In angular, I have a list of checkboxes that are all binded to a value of boolean value that I get from a json:
<div ng-repeat="err in rec.errorList"><input type="checkbox" ng-model="err.ignore" name="{{err.errorCode}}" ng-value="err.errorCode" check-all="{{err.errorCode}}" /></div>
But mean while, I am trying to check all those checkboxes with the same name, when checking one of the checkboxes!
what is the best way of doing that in angular way? I mean is there a way of binding all these checkboxes with the same Name attribute for example, together?
I tried to write a directive, something like this but don't know how should I continue on that:
.directive("checkAll", function(){
return {
link: function(scope, element, attr){
element.bind('change', function(){
var errorCode = attr["checkAll"];
var elms = scope.errorCode;
})
}
}
})
Here is a plunker of what I actually want to do
http://plnkr.co/edit/sLXGlXRh9vu7FETDmJd1?p=preview
I can have many lists, and what I want to is whenever I click on one of these checkboxes, update all the checkboxes with the same errorCode maybe without looping on all those errorLists again.

You could do this simply using the same ng-model for each name.
This will look like this :
Controller
$scope.errorList = [{errorCode:1},{errorCode:2},
{errorCode:1},{errorCode:3},{errorCode:1},{errorCode:1},
{errorCode:2},{errorCode:1},{errorCode:3},{errorCode:3}];
$scope.checkboxByName = {};
View
<div ng-repeat="err in errorList">
<input type="checkbox" ng-model="checkboxByName[err.errorCode]">
</div>
If you really need the error.ignore var on each error, you could add this function :
$scope.updateIgnore = function(){
angular.forEach($scope.errorList, function(error){
error.ignore = $scope.checkboxByName[error.errorCode];
})
}
And a ng-change on all your inputs :
ng-change="updateIgnore()"
Here is a plunker showing the full implementation
Hope it helped.

Related

AngularJS, radio buttons with getters and setters

I am trying to have radio buttons controlling boolean properties of external objects through getters and setters.
In the following example, there are multiple Person, but only one can be happy. One of them is already happy. I want to control which one is happy through radio buttons.
Therefore, I created a template with a radio input, but I can't bind it to person.happy, as I don't have access to this property (suppose Person is a third-party library).
In order to bind something to the input, I created a dumb controller, to provide a default value (the Person value), and propagate changes to the real variable (in short, call the setter). I can't seem to get things right.
var app = angular.module('app', []);
// Person constructor ; happy is a private member
function Person(name, isHappy) {
var happy = isHappy;
this.name = name;
this.getHappy = function() { return happy; }
this.setHappy = function(value) { happy = value; }
}
// Inject a person list in the scope
app.controller('PeopleCtrl', function($scope) {
$scope.people = [
new Person('Alice', true),
new Person('Bob', false),
new Person('Carol', false)
];
});
// Template to display one person + happiness controller
var humanTemplate = [
'<div><label>',
'<input type="radio" name="group" ng-model="me.happy" ng-change="me.toggle()" value="{{ !me.happy }}">',
'<span>{{ person.name }} is {{ me.happy ? "happy" : "sad" }}</span>',
'</label></div>'
].join('');
// Directive to display one person
app.directive('human', function() {
return {
restrict: 'E',
template: humanTemplate,
scope: {
person: '='
},
controllerAs: 'me',
controller: function($scope) {
var me = this;
me.happy = $scope.person.getHappy();
me.toggle = function() {
console.log(me.happy);
$scope.person.setHappy(me.happy);
};
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.min.js"></script>
<div ng-app="app" ng-controller="PeopleCtrl">
<human ng-repeat="p in people" person="p"></human>
</div>
I would have thought ngChange would be tied to the checked state on radio and checkboxes (would have been somehow logical), but there are symptoms telling me otherwise:
ngChange is not fired for the previously checked radio
ngChange is not fired more than once (I got it to fire twice when messing with the input value attribute)
I may be mistaken since I am a beginner, but it looks like ngChange is not what I want.
What do you think would be a good solution for this problem?
Do I need to write a parent controller to manage manually the deactivation of some items when the other get activated?
I think (at least what I see from demo) you use radio buttons with different purpose.
Radio buttons works in groups where each button has shared ng-model. In your case human directive has own scope and therefore each radio button has own model a.e. stand-alone, that breaks radio buttons concept and usage.
Change to classic checkbox type="checkbox" and everything will work properly
Demo Plunker

Bind dynamically set ng-model attribute for text field

After searching around for hours I am still unable to find an answer to my problem. I am populating a dynamic form with text fields based on values from a database, but am unable to successfully bind the fields to my model. Here's the scenario:
I've got a "project" model in my controller containing lots of project related information (name, start date, participants, category etc), but let's just focus on the "project.name" property for now. In the database I configure "forms" with a number of associated fields, where each field has a property that points to which property it corresponds to in my view model (e.g. "project.name"). At runtime I add these fields to an HTML form dynamically and attempt to set the ng-model attribute to the "modelBinding" value, in this case "project.name".
<div ng-repeat="formField in form.formFields">
<input ng-model="formField.modelBinding" /></div>
This will result in a text box being added to my form, with ng-model="formField.modelBinding" and the textbox value = 'project.data'.
What I am trying to achieve is to set ng-model = 'project.data', in other words replace formField.modelBinding with the value of formField.modelBinding.
One approach that seemed logical was
<input ng-model = "{{formField.modelBinding}}" />
but this is obviously not going to work. I've tried to insert the HTML tags with ng-bind-html but this seems to only work with ng-bind, not ng-model.
Any suggestions?
Assuming that you are trying to bind a value to a model from a name that you have within the formField you can create a directive (aka ngModelName) to bind your model by name from this value.
Observation: My first thought was using a simple accessor like model[formField.modelBinding] which would simple bind the formField.modelBinding into a model member on scope. However, I didn't use the property accessor because it would create a property named by formField.modelBinding value and not the correct object hierarchy expected. For example, the case described on this question, project.data would create an object { 'project.data': 'my data' } but not { 'project': { data: 'my data'}} as it should.
angular.module('myApp', [])
.directive('ngModelName', ['$compile', function ($compile) {
return {
restrict: 'A',
priority: 1000,
link: function (scope, element, attrs) {
scope.$watch(attrs.ngModelName, function(ngModelName) {
// no need to bind a model
if (attrs.ngModel == ngModelName || !ngModelName) return;
element.attr('ng-model', ngModelName);
// remove ngModel if it's empty
if (ngModelName == '') {
element.removeAttr('ng-model');
}
// clean the previous event handlers,
// to rebinded on the next compile
element.unbind();
//recompile to apply ngModel, and rebind events
$compile(element)(scope);
});
}
};
}])
.controller('myController', function ($scope) {
$scope.form = {
formFields: [
{
modelBinding: 'model.project.data'
}
]
};
$scope.model = {};
});
angular.element(document).ready(function () {
angular.bootstrap(document, ['myApp']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="myController">
<div ng-repeat="formField in form.formFields">
<input ng-model-name="formField.modelBinding" placeholder="{{ formField.modelBinding }}" />
</div>
<div>
<pre>{{ model | json }}</pre>
</div>
</div>
I guess the "modelBinding" attribute has the model name of the formfield, so, in that case you should do this:
<div ng-repeat="formField in form.formFields">
<input ng-model="form.formFields[formField.modelBinding]" />
</div>
Use the modelBinding as the key to retrieve from formFields.

Retrieve all inputs values from a form AngularJS

I have a massive form with more or less 80 / 90 inputs.
My main problem is How can I pass all those inputs belonging to unique form in an ajax request without map the inputs manually into a object?
I know that with jquery you can use serialize() function selecting the form. Is there any helper function in angular to achieve that?
Thanks
Indeed, as Michal's comment suggested, it doesn't look like you are using ng-model.
Angular's jqLite doesn't support serialize() since with Angular one would typically build a ViewModel that would then be bound to a form.
But, if you are out of luck, you could add jQuery.js to get the serialize() support and create a simple directive - serializer - that would act on a form.
app.directive("serializer", function(){
return {
restrict: "A",
scope: {
onSubmit: "&serializer"
},
link: function(scope, element){
// assuming for brevity that directive is defined on <form>
var form = element;
form.submit(function(event){
event.preventDefault();
var serializedData = form.serialize();
scope.onSubmit({data: serializedData});
});
}
};
});
And use it as follows:
<form serializer="submit(data)">
<input name="foo">
<input name="bar">
<button type="submit">save</button>
</form>
And in the controller:
$scope.submit = function(data){
console.log(data);
}
plunker
EDIT:
If you are using ng-model and in fact have a proper ViewModel, then this is the "Angular way" and so, you should have some object that is bound to the form inputs - you should just submit that.
<form name="foo" ng-submit="onSubmit()">
<input ng-model="fooObject.a">
<input ng-model="fooObject.b">
...
</form>
$scope.fooObject = {};
$scope.onSubmit = function(){
$http.post("url/to/server", {data: $scope.fooObject})
.success(...);
}

AngularJS directive remove class in parent element

I am using the following code to add / remove class "checked" to the radio input parent. It works perfectly when I use JQuery selector inside the directive but fails when I try to use the directive element, can someone please check my code and tell me why it is not working with element and how I can possibly add/ remove class checked to the radio input parent while using element instead of the jquery selectors? Thanks
.directive('disInpDir', function() {
return {
restrict: 'A',
scope: {
inpflag: '='
},
link: function(scope, element, attrs) {
element.bind('click', function(){
//This code will not work
if(element.parent().hasClass("checked")){
scope.$apply(function(){
element.parent().removeClass("checked");
element.parent().addClass("checked");
});
}else{
scope.$apply(function(){
element.parent().addClass("checked");
});
}
//This code works perfectly
$('input:not(:checked)').parent().removeClass("checked");
$('input:checked').parent().addClass("checked");
});
}
};
});
HTML:
<div class="inpwrap" for="image1">
<input type="radio" id="image1" name="radio1" value="" inpflag="imageLoaded" dis-inp-dir/>
</div>
<div class="inpwrap" for="image2">
<input type="radio" id="image2" name="radio1" value="" inpflag="imageLoaded" dis-inp-dir/>
</div>
Your code actually works for me in Plnkr (more or less):
http://plnkr.co/edit/vJJRYQQxH7u2bKSc27UA?p=preview
When you run this, the 'checked' class gets correctly added to the parent DIVs using only the first code you included. (I commented out the jQuery mechanism - I didn't add jQuery to this page, as a test.)
However, I think what you're trying to accomplish isn't working out because you're only capturing click events. The radio button that loses its checked attribute doesn't get a click event, only the next one does. In jQuery your selector is really broad - you're hitting every radio button, so it does what you want. But since you only trap click on the radio button that receives the click, it doesn't do what you want using the other pattern. checked gets added, but never removed.
A more Angular-ish pattern would be something like this:
http://plnkr.co/edit/HN7tLxkRA0jUL5GPjk5V?p=preview
link: function($scope) {
$scope.checked = false;
$scope.$watch('currentValue', function() {
$scope.checked = ($scope.currentValue === $scope.imgNumber);
});
$scope.setValue = function() {
$scope.currentValue = $scope.imgNumber;
};
}
What you see here lets Angular do all the dirty work, which is kind of the point. You can actually go a lot further than this - you could probably cut half the code out and do it all with expressions. The point is that in Angular, you really want to focus on the DATA (the model). You wire all of your behaviors and events up (controller) to things that manipulate that data, and then wire up all your DOM styles, classes, templates (view), etc. up to conditionals against that same data. And that is the point of MVC!

Checkbox list breaks, why? And optimal Angularjs way?

I don't understand this, but I suspect I'm doing something wrong, or a non-angularjs way.
I have a checkbox list inside ng-repeat. It controller loads the list from a JSON. Pretty straightforward really. I'm then using a directive (car-select) on each of the resulting checkboxes. This directive calls a function inside the main $scope (selectBrand()). This cycles through the selected checkboxes, and if checked==true, add to $scope.brand. I've added a textbox so that $scope.brand fills it, and i've set it to required so that it fires the built in validation e.g:
HTML:
<div ng-repeat="v in viewModel">
<label class="checkbox">
<input type="checkbox" ng-model="v.c" ng-checked="v.c" />{{v.n}}
</label>
</div>
<input type="text" name="brands" ng-model="brands" car-select required/> <br>
JS:
$scope.selectBrand = function() {
var selectedBrands = [];
angular.forEach($scope.viewModel, function(v){
if (v.c)
selectedBrands.push(v.v);
})
if (selectedBrands.length > 0)
$scope.brands = selectedBrands;
else
$scope.brands = null;
}
DIRECTIVE:
app.directive('carSelect', function() {
return function(scope, element) {
element.bind('change', function() {
scope.selectBrand();
})
}
});
Here's the weird part which I don't understand. It took a while to figure out that this particular line was making this whole thing work. If I add the following in the page, everything works great. But if i remove it, the whole thing breaks. WHY?!
<div>{{selectBrand()}}</div>
It's like the whole thing doesn't bind unless the above is called in the HTML. It's called in the directive, and I've tried putting that call inside the clickButton() function, but eventually it breaks. Either way, the live update of the textbox seems to fail if the above is removed. I'd love to get a good explanation of how I'm doing something wrong and how I could fix it :)
PLUNKER:
http://plnkr.co/edit/4QISKcq7YYH678YLsTTF?p=preview
Ok, i create fork ;-)
update variable with only data checked
your model :
{"cars":
[
{"v":"m","n":"Mini","c":false},
{"v":"c","n":"Corvette","c":true},
{"v":"b","n":"BMW","c":true},
{"v":"l","n":"Lamborghini","c":true},
{"v":"f","n":"Ferrari","c":false}
]
}
you want only checked :
$scope.brands = $filter('filter')($scope.viewModel, {c: true});
when model change you want to update your variable so use watch in controller
$scope.$watch('viewModel', function(newval, oldval){
if (oldval != newval)
{
$scope.brands = $filter('filter')($scope.viewModel, {c: true});
}
},true
);
});
see http://plnkr.co/edit/PnABre?p=preview

Resources