Bind dynamically set ng-model attribute for text field - angularjs

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.

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

AngularJS directive inside a ng-repeat loop with variable-based templateUrl

Ok, I'm kinda new to the whole AngularJS thing and I'm probably getting my hands dirtier than I should, but here's what I'm trying to do:
loop through an array of fields (array of objects actually) for a customer entity
call a directive that chooses the template from a field property
data-bind the actual customer field to the ng-model inside the template
display the field
Here's what I have so far:
looping HTML
<div ng-repeat="info in customerCtrl.personalInfoFields">
<edit-field info="info" model="customerCtrl.customer"></edit-field>
</div>
stripped down controller:
customerCtrl.personalInfoFields = [{'field':'NAME', 'type':'text'},
{'field':'SURNAME', 'type':'text'},
{'field':'MAIL', 'type':'email'}]
customerCtrl.customer = customersFactory.customerDetails; // filled through a Factory, works fine if I just draw every single field manually in the HTML
directive:
myApp.directive('editField', ['$http', '$compile', 'capitalizeFilter', function($http, $compile, $capitalizeFilter) {
return {
scope: {
info: "=",
model: "="
},
replace: true,
template: '<div ng-include="url"></div>',
link: function(scope, element, attrs){
scope.url = '/views/fields/edit'+$capitalizeFilter(scope.info.type)+'.html';
}
/*templateUrl: function(elem,attrs)
{
if(typeof attrs.info.type === "undefined")
return '/views/fields/editText.html';
return '/views/fields/edit'+attrs.info.type+'.html'
},*/
};
}]);
editText.html (the other files don't differ much right now, will do later)
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
<div class="form-group">
<label class="control-label">{{'customers.'+info.field | i18n | capitalize}}</label>
<input type="text" class="form-control" ng-model="model[info.field]"/>
</div>
</div>
Right now, inside the field I just get [object Object], if I pass the actual field itself as model instead of the whole customer object it displays fine but it isn't binded (I can change the field content but it won't reflect in the controller object)
The commented templateUrl part doesn't work, since AngularJS only compiles the URL once for the whole ng-repeat function so I'm stuck with the undefined variable -> editText for everyone
How can I successfully bind the fields while still grabbing the right template for each field type?
Problem solved, apparently calling
$compile(element.contents())(scope);
fixed the binding, but I still had to pass the whole customer object to the directive and then use
ng-model="model[info.field]"
to get the desired result

HTML parsed through $compile, ng-model not binding in isolation for ng-repeat

I am building an Angular module that will allow a form to be built dynamically.
As elements are selected, HTML is added to a model. The model is attached to an ng-repeat element.
<div ng-repeat="item in list1 track by $index">
<div compiledom="item.content"></div>
</div>
So an item in the model might look like this:
{
'title': 'Full-Column Text',
'drag': true,
'inputValue': '',
'content': '<div class="small-12 columns"><input type="text" dynamic-model="$index" placeholder="Full Width Text" /></div>'
}
I am using a custom directive to compile the HTML fed to the model.
.directive('compiledom', function($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compiledom);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
}
})
And using a second directive to bind the model data to the input field in that HTML.
.directive('dynamicModel', function($compile) {
return function(scope, element, attrs) {
scope.$watch(attrs.dynamicModel, function(dynamicModel) {
if (attrs.ngModel || attrs.ngModel == dynamicModel || !dynamicModel) return;
element.attr('ng-model', 'item.inputValue'); <---------- bound here
if (dynamicModel == '') element.removeAttr('ng-model');
element.unbind();
$compile(element)(scope);
});
}
})
My issue is that whatever I put into an input field gets placed to every input element. For some reason, it appears that a single item.inputValue is getting reflected to every item of the same type. The model is bound, but I have broken something in ng-repeat that keeps it in isolation.
For example, if I have two 'Full-Column Text' inputs, if one is set to 'ABC', both are set to 'ABC'. If I also were to have 2 'Half-Column Text' inputs, they would remain unset until I set one of them to 'DCE' - then they are both set to 'DCE'.
A link to a demo/example of the issue will be shared soon.
As it turned out, my directives were fine.
When I was adding to my model, I was using .slice. This was causing a reflection issue. Using angular.copy made a geniune clone, allowing the isolation I was looking for.
$scope.list1[x] = angular.copy($scope.list5[x]);

Angular way of binding elements of the same name together

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.

Custom Directive : Dropdown selected value not binding

I'm trying to create a custom directive for a drop down control in AngularJS 1.4.4. I can handle the selected event, but i can not get the binding for what is selected in the drop down list.
I want to call this from Html markup the following way.
<my-dropdown-list source="myList" destination="mySelection" />
The angular js custom directive is here.
(function() {
var directive = function($compile) {
return {
restrict: 'E',
scope: {
model: '=source',
selectedValues: '=destination'
},
controller: function($scope) {
$scope.onSelChange = function() {
alert('called');
console.log($scope.selectedItem.Code, $scope.selectedItem.Name);
};
// $scope.selectedItem is always undefined here.
},
link: function ($scope, $elem) {
var rowHtml =
'<select ng-options="item as item.Name for item in model" ng-model="selectedItem" ng-change="onSelChange()"></select>';
$elem.html(rowHtml);
$compile($elem.contents())($scope.$new());
}
};
};
my.directive('myDropdownList', directive);
})();
I'm new to Angular, so this may be something small that i missed here, but i can't seem to get a value for 'selectedItem'
I find out this in AgularJS document
Note that the value of a select directive used without ngOptions is
always a string. When the model needs to be bound to a non-string
value, you must either explictly convert it using a directive (see
example below) or use ngOptions to specify the set of options. This is
because an option element can only be bound to string values at
present.
Link: https://docs.angularjs.org/api/ng/directive/select
You should use ngRepeat to generate the list like this post:
Angularjs: select not updating when ng-model is updated

Resources