AngularJS operations with directive attributes - angularjs

Small question. I can't find any solution
I have some directives that I need to insert into my tags in some cases.
My form object
function AppCtrl($scope) {
$scope.form ={
name:{
type: 'text',
validate: 'letters'
},
tel:{
type: 'number',
validate: 'digits'
}
}
}
My directive
<body ng-app ng-controller="AppCtrl">
<input type="{{v.type}}" placeholder="{{k}}" {{v.type=='number' ? 'string-to-number' : '' }} {{v.validate}} value="" ng-repeat="(k,v) in form">
</body>
I need to render 'validate' and 'string-to-number' atributes in inputs. But my code doesn't fire
Here is jsfiddle example
http://jsfiddle.net/FxM3B/517/

I updated the plunker with a possible solution
<div ng-repeat="(k,v) in form">
<input ng-if="v.type ==='number'" type="{{v.type}}" placeholder="{{k}}" validate="{{v.validate}}" value="" string-to-number>
<input ng-if="v.type !=='number'" type="{{v.type}}" placeholder="{{k}}" validate="{{v.validate}}" value="">
</div>
Check with ng-if the value and add markup for the two options.

You can't add an HTML attribute this way, you should take a look at ng-attr, but you might need to change your directive because it adds the attribute in all case, only the content is dynamic.
Otherwise this article explain several solutions : http://www.thinkingmedia.ca/2015/03/conditionally-add-a-html-element-attribute-value-in-angularjs/

Since the names of attributes you want to add to an element are the values of properties of your model, you can't use ng-attr directive.
You can author you own directive that handles such situations.
Here's jsfiddle example
.directive('boolAttrs', function(){
return function(scope, element, attrs){
var propExpr = attrs['boolAttrs'];
if (propExpr)
{
var data = angular.fromJson(propExpr);
if (angular.isArray(data) && data.length > 0) {
angular.forEach(data, function (value, index, array) {
var propValue = scope.$eval(value);
if (angular.isFunction(propValue))
propValue = propValue();
if (propValue)
element.attr(propValue, "");
});
}
}
};
})
You can apply this directive this way:
bool-attrs='["v.validate", "v.type == \"number\" ? \"string-to-number\" : \"\" "]'
A value for bool-attrs should be a valid json array string, that defines expressions to evaluete to determine the names of attributes to be inserted.

Related

AngularJS ng-attr- is not working

I need to set multiple attribute for select tag dynamically.
According to this documentation:
https://www.thinkingmedia.ca/2015/03/conditionally-add-a-html-element-attribute-value-in-angularjs/#using-the-ngattr
This should work:
<select ng-attr-multiple="param.MultiValue">
and become
<select multiple>
if param.MultiValue evaluates to true, otherwise - without multiple attribute.
Doesn't work, I get select tag without multiple attribute regardless.
Even if I use
<select ng-attr-multiple="true">
Any idea why?
Thanks.
Web browsers are sometimes picky about what values they consider valid for attributes. ng-attr is for binding arbitrary attributes, select does not "know" that ng-attr-multiple is trying to set the multiple attribute, it just reacts on multiple directly.
Instead I would suggest you to write a wrapper directive which will add the multiple attribute to your element.
For more information on this see this documentation link:
https://docs.angularjs.org/guide/interpolation
Issue link:
https://github.com/angular/angular.js/issues/13570
var mymodule = angular.module('myApp', []);
function sampleController($scope, $timeout) {
$scope.addAttribute = true;
}
mymodule.controller('sampleController', sampleController)
.directive('multipleAttr', function() {
return {
'restrict': 'A',
'compile': function() {
return {
'pre': function($s, $el, attrs) {
if (attrs.multipleAttr === 'true' || attrs.multipleAttr === true) {
$el.attr('multiple', true);
}
}
}
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="sampleController">
<select multiple-attr="{{addAttribute}}">
<option>Hi</option>
<option>Hello</option>
<option>Hey</option>
</select>
</div>
</div>

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.

Can't bind inside ng-show that is in ng-repeat

So I am working on a small login form using AngularJS and it seemed extremely natural to remove duplicated code by using an ng-repeat directive. Everything is very natural and works well except any kind of binding inside ng-show, this is where things gets unintuitive and break down. A fiddle of the work so far can be found here.
What I am wondering is why does everything break down for ng-show? If I dump the ng-repeat and duplicate the code everything works fine for all three ng-show instances assuming that I manually type the references to the elements and values.
Below is a copy of the fiddle html and javascript:
<div ng-app='loginApp' ng-controller='loginController'>
<form name='loginForm' novalidate>
<div class='form-group' ng-repeat='field in fields'>
<label>{{field.label}}</label>
<input type='{{field.inputType}}' class='form-control' name='{{field.inputName}}' ng-minlength='{{field.minlength}}' ng-maxlength='{{field.maxlength}}' ng-model='field.value' ng-focus='inputFocused'/>
<!-- The ng-show below doesn't work as expected -->
<div ng-show="canShowUserMsgs(inputFocused, loginForm.{{field.inputName}}.$dirty, loginForm.{{field.inputName}}.$invalid)">
<!-- The ng-show below doesn't work as expected -->
<p ng-show="loginForm.{{field.inputName}}.$error.minlength" class='help-block'>Must be more than {{field.minlength}} characters long.</p>
<!-- The ng-show below doesn't work as expected -->
<p ng-show="loginForm.{{field.inputName}}.$error.maxlength" class='help-block'>Must be less than {{field.maxlength}} characters long.</p>
</div>
</div>
</form>
</div>
var loginApp = angular.module('loginApp', []);
loginApp.controller('loginController', function($scope, $http) {
$scope.fields = [
{
label : "User Name",
inputType : "text",
inputName : "userName",
value : "",
minlength : 5,
maxlength : 15
},
{
label : "Password",
inputType : "password",
inputName : "password",
value : "",
minlength : 5,
maxlength : 15
}
];
$scope.canShowUserMsgs = function(inputFocused, inputDirty, inputInvalid) {
return (inputDirty && inputInvalid && !inputFocused); };
});
loginApp.directive('ngFocus', [function() {
return {
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
var modelName = attrs['ngFocus'];
scope[modelName] = false;
element.bind('focus', function(evt) {
scope.$apply(function() {scope[modelName] = true;});
}).bind('blur', function(evt) {
scope.$apply(function() {scope[modelName] = false;});
});
}
}
}]);
you have to add ng-form inside the ng-repeat and need to do some modifications to how you use the ng-show. Check the working sample of your code.
working copy
This doesn't work: <p ng-show="loginForm.{{field.inputName}}.$error.minlength" because the interpretation of the curly brace expression only happens once, there's not two passes: one to generate the bind path without any curly braces, then a second to evaluate the bind path against the scope. It's not like that. Everything happens in a single pass. So, convert it to a controller function call:
<p ng-show="minLength(field)"

How to use a dynamic value with ngClass

I'm trying to apply a class name that's the same as a scope variable.
For example:
<div ng-class="{item.name : item.name}">
So that the value of item.name is added to the class. This doesn't seem to do anything though. Any suggestions on how to do this?
Thanks!
EDIT:
This is actually being done within a select, using ng-options. For example:
<select ng-options="c.code as c.name for c in countries"></select>
Now, I want to apply a class name that has the value of c.code
I found the following directive, which seems to work, but not with interpolation of the value:
angular.module('directives.app').directive('optionsClass', ['$parse', function ($parse) {
'use strict';
return {
require: 'select',
link: function(scope, elem, attrs, ngSelect) {
// get the source for the items array that populates the select.
var optionsSourceStr = attrs.ngOptions.split(' ').pop(),
// use $parse to get a function from the options-class attribute
// that you can use to evaluate later.
getOptionsClass = $parse(attrs.optionsClass);
scope.$watch(optionsSourceStr, function(items) {
// when the options source changes loop through its items.
angular.forEach(items, function(item, index) {
// evaluate against the item to get a mapping object for
// for your classes.
var classes = getOptionsClass(item),
// also get the option you're going to need. This can be found
// by looking for the option with the appropriate index in the
// value attribute.
option = elem.find('option[value=' + index + ']');
// now loop through the key/value pairs in the mapping object
// and apply the classes that evaluated to be truthy.
angular.forEach(classes, function(add, className) {
if(add) {
angular.element(option).addClass(className);
}
});
});
});
}
};
}]);
Better later than never.
<div ng-class="{'{{item.name}}' : item.condition}">
yes. ' and {{ for classname.
I'm on angular 1.5.5 and none of these solutions worked for me.
It is possible to use the array and map syntax at once though it's only shown in the last example here
<div ng-class="[item.name, {'other-name' : item.condition}]">
Simply using the variable should be sufficient:
<div ng-class="item.name" />
This is also documented in the official documentation.
I think you missed the concept.
A conditional css class looks like this:
<div ng-class="{'<css_class_name>': <bool_condition>}">
And I dont think you want:
<div ng-class="{'true': true}">
You probally want to use:
<div ng-class="item.name"></div>
Angularjs Apply class with condition:
<div ng-class="{true:'class1',false:'class2'}[condition]" >
This can be useful in some cases:
HTML:
<div ng-class="getCssClass()"></div>
JS:
$scope.getCssClass = function () {
return { item.name: item.name };
};

How to validate dynamic form fields in angular directive?

I would like to create form with fields created in directive. Data binding of data working correctly but validation doesn't work.
this is html:
<body ng-controller="MainCtrl">
<h1>form</h1>
<form name="form">
<div ng-repeat="conf in config">
<div field data="data" conf="conf"></div>
</div>
</form>
<pre>{{data|json}}</pre>
</body>
controller and field directive:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.data = {name: '', age: ''}
$scope.config = [
{field: 'name', required:true},
{field: 'age'}
];
});
app.directive('field', function ($compile) {
return {
scope: {
data: '=',
conf: '='
},
link: function linkFn(scope, element, attrs) {
// field container
var row = angular.element('<div></div>');
// label
row.append(scope.conf.field + ': ');
// field input
var field = angular.element('<input type="text" />');
field.attr('name', scope.conf.field);
field.attr('ng-model', 'data.' + scope.conf.field);
if (scope.conf.required) {
field.attr('required', 'required');
}
row.append(field);
// validation
if (scope.conf.required) {
var required = angular.element('<span>required</span>');
required.attr('ng-show',
'form.' + scope.conf.field + '.$error.required');
row.append(required);
}
$compile(row)(scope);
element.append(row);
}
}
});
problem is that validation for field name doesn't work and validation text required is never shown. May be form in ng-show is unknown in directive. But I don't know how to pass form into field directive. Can you help me how to fix it? Thanks.
here is live code: http://plnkr.co/edit/j0xc7iV1Sqid2VK6rMDF?p=preview
Todo:
before:
$compile(row)(scope);
element.append(row);
after:
element.append(row);
$compile(row)(scope);
p/s in 'planker' for facilities add css:
.ng-invalid {
border: 1px solid red;
}
You'll need to use ng-form directive and push the dynamic field directly into form object.
This thread can help you out:
https://github.com/angular/angular.js/issues/1404
Here is a plunker forked from yours to fix you're issue:
http://plnkr.co/edit/qoMOPRoSnyIdMiZnbnDF?p=preview
To summarize, I added a watch that will toggle the error message instead of using the ng-show directive. Things can get hairy when you attempt to dynamically add a directive within a directive link. For a simple use case as this, it is quicker to add your own watch.
You may also look at this directive which is preconfigured to handle many use cases for validation as well as allow you to create custom validations easily https://github.com/nelsonomuto/angular-ui-form-validation
var toggleRequiredErrorMessage = function (invalid) {
if(invalid === true) {
addRequiredErrorMessage();
} else {
removeRequiredErrorMessage();
}
};
scope.$watch( watchIfRequired, toggleRequiredErrorMessage );

Resources