I would like to know if something like this is possible in angular
<form name='form'>
<input-directive-with-ngmessages1 ... inputname='field1' form='form' />
<input-directive-with-ngmessages2 ... inputname='field2' form='form' />
</form>
I know that validation system in angular is working based on form name, but I would like to make components from inputs, move ng-messages inside custom input directives, passing just form name to it, with possiblity to get overall form validation status,
I was looking about dynamic forms/validation on stackoverflow, but I didn't find something like above example, thx in advance
Definitely doable. My suggestion: Require an ngModel to make a custom control (as described here) use transclusion for the messages. Sample code:
app.directive('inputDirectiveWithNgmessages', function() {
return {
restrict: 'E',
template:
'<input type="text" ng-model="ctrl.model" name="ctrl.inputname" />' +
'<div ng-messages="ctrl.$error" role="alert" ng-transclude>' +
// element content, i.e. the messages will be transcluded here
'</div>',
transclude: true,
scope: {},
require: ['ngModel', 'inputDirectiveWithNgmessages'],
link: function(scope, elem, attrs, ctrls) {
var ngModel = ctrls[0];
var inputDirectiveWithNgmessages = ctrls[1];
inputDirectiveWithNgmessages.inputname = attrs.inputname;
inputDirectiveWithNgmessages.setModel(ngModel);
},
controllerAs: 'ctrl',
controller: function($scope) {
var self = this;
this.model = null;
this.setModel = function(ngModel) {
this.$error = ngModel.$error;
ngModel.$render = function() {
self.model = ngModel.$viewValue;
};
$scope.$watch('ctrl.model', function(newval) {
ngModel.$setViewValue(newval);
});
};
}
};
});
Usage is quite simple - place the messages inside the element:
<input-directive-with-ngmessages ng-model="model1" inputname="field1" ng-required="true">
<div ng-message="required">Required field</div>
</input-directive-with-ngmessages>
No need to specify the form name.
And a fiddle: http://jsfiddle.net/12sf82p3/
THE CATCH: Standard validators we have been comfortably using with inputs, e.g. ng-pattern, do NOT work out of the box in this (except for the ng-required). You see ng-pattern is not a directive; it is handled as an attribute by Angular's standard input directive. The workaround is to implement the validators you want as directives and place them on the <input-directive-with-ngmessages>, e.g.:
<input-directive-with-ngmessages ng-model="..." inputname="..." my-pattern="[A-Z][0-9]">
The my-pattern directive will use the standard $validators pipeline of ngModel to implement regular expression validation.
A benefit of this is that the template of the directive can be tweaked to suit any needs. E.g. it could create form element markup for Twitter's Bootstrap.
Finally you may want to take a look at egkyron for an alternative to form validation, i.e. model based validation.
Related
I have component that is used multiple times, 58 times to be exakt. The only thing that differs between them is unique attributes to add validations. What I want to do is to add a array of attributes to my template before is compiled. Is this possible to achieve when working with a Angular component?
component.js
(function () {
'use strict';
angular
.module('upaApp')
.component('component', {
bindings: {
inputAttributes: '#',
},
controller: controller,
restrict: 'E',
templateUrl: 'app/component/component.html'
});
function controller() {
var $ctrl = this;
}
})();
component.html
<input {{ $ctrl.inputAttributes }}
class="form-control"
type="text" />
When I use the component <component input-attributes="directive1, directive2"></component> it doesn't render out my string and even if it did I would not be sure that it would work. So is there a way to dynamically be able to set the attributes in AngularJS?
Is this angular 1 or 2?
Ill assume the former.
I dont know of a way to place a string as an attribute. What you could do as a workaround is conditionally insert attributes with the ng-attr- attribute. This will insert the attribute if the variable is not undefined.
maybe something like this:
$scope.ctrl.inputAttributes = {
directive1:undefined, //this one wont show
directive2:"this one will show"// as directive2="this one will show"
}
then in your markup:
<input ng-attr-directive1="ctrl.inputAttributes.directive1"
ng-attr-directive2="ctrl.inputAttributes.directive2"
class="form-control"
type="text" />
https://www.thinkingmedia.ca/2015/03/conditionally-add-a-html-element-attribute-value-in-angularjs/
EDIT: it may not be clean, but you could create a directive that compiles html.
app.directive('dynamicAttributes', function ($compile) {
return {
restrict: 'E',
scope: {
attributes: '#'
},
link: function (scope, elem, attrs) {
var h = '<input '+scope.attributes + ' class="form-control" type="text" />';
elem.replaceWith($compile(h)(scope));
}
}
});
then in your DOM
<dynamic-attributes attributes="1 2 3"></dynamic-attributes>
fiddle: http://jsfiddle.net/brhardwick/nx16zdrL/1/
There was actually a very simple solution on my problem. You could use ng-model to send the value to the component. And when I placed my directives on the component it validates accordingly since it can access the value from ng-model.
INTRODUCTION
I need to find a way to validate forms that are created dynamically in the directive which holds them all together.
Validation errors should appear on form submit of each separate form, i have no problems displaying errors
DIRECTIVE
Contains multiple forms which are all submitted separately
app.directive('multiFormWrapper', function() {
return {
templateUrl: 'templates/directives/multiform-wrapper.html',
restrict: 'A',
scope: {
pageId: '#',
attributeName: '#',
multivaluedAttribute: '=',
callbacks: '='
}
};
});
TEMPLATE
Only the important parts, look at the name and ng-submit
<div class="row" ng-repeat="group in multivaluedAttribute">
<form
name="{{attributeName}}EditForm{{group.index}}"
novalidate
id="{{pageId}}-form{{attributeName}}Edit{{group.index}}"
ng-submit="callbacks.updateMultivalued(attributeName, value)">
<div class="row" ng-repeat="field in group.fields">
<div input-field-directive input-field="field"></div>
</div>
</form>
PROBLEM
In order to achieve that validation messages appear on forms submit i would like send the data only if form.$valid:
ng-submit="{{attributeName}}EditForm{{group.index}}.$valid &&
callbacks.updateMultivalued(attributeName, value)">
But the name of the form is created in expression though.
QUESTION
Is there a way to alias the form name somehow? (I don't think so)
Should i create another directive that would wrap one single form, get a handle of wrapped FormController within and override submit? It would be great if there is a way to do this without creating additional directive but for all form instances in the link or compile of multiFormWrapper
It seems that my approach was wrong, in code.realcrowd.com blog post, the author is using an approach to replace ng-submit with custom directive in which he cancels the submit if form is invalid
app.directive('rcSubmit', ['$parse', function($parse) {
return {
restrict: 'A',
require: 'form',
link: function (scope, formElement, attributes, formController) {
var fn = $parse(attributes.rcSubmit);
formElement.bind('submit', function (event) {
// if form is not valid cancel it.
if (!formController.$valid) return false;
scope.$apply(function() {
fn(scope, {$event:event});
});
});
}
};
}]);
I'm having issues with a directive I am writing.
Within the directive's template there is also another element directive.
Essentially the outer directive is a decorator for the inner, adding more functionality..
The issue that I am having is that the $pristine and $dirty values are not being set as I would have expected.
I have amended the fiddle below to demonstrate a similar scenario..
(Code follows:)
HTML
<body ng-app="demo" ng-controller="DemoController">
<h3>rn-stepper demo (3/5)</h3>
Model value : {{ rating }}<br>
<hr>
<div ng-model="rating" rn-stepper></div>
</body>
JS
angular.module('demo', [])
.controller('DemoController', function($scope) {
$scope.rating = 42;
})
.directive('test', function() {
return {
restrict: 'E',
scope: {
ngModel: '=ngModel'
},
template: '<input type="text" ng-model="ngModel"></input>'
};
})
.directive('rnStepper', function() {
return {
restrict: 'AE',
scope: {
value: '=ngModel'
},
template: '<button ng-click="decrement()">-</button>' +
'<div>{{ value }}</div>' +
'<button ng-click="increment()">+</button>' +
'<test ng-model="value"></test>',
link: function(scope, iElement, iAttrs) {
scope.increment = function() {
scope.value++;
}
scope.decrement = function() {
scope.value--;
}
}
};
});
http://jsfiddle.net/qqqspj7o/
The model is shared as expected and when I change the value in either the text input or using the slider, the binding works - however if I update the value in the text input, only the text input is marked as ng-dirty - the element directive itself remains as ng-pristine as does the outer div.
I don't understand why this is and the values are not propagated to the element? Is that expected behaviour - if so, how do I propagate the ng-dirty etc values to the element directive and the outer div..
Note: I can only use Angular v 1.2.x as the code needs to be compatible with IE8.
Thanks in advance..
Generally in directives you should avoid =value binding, and work directly with ngModelController.
This topic is a bit complicated for discussion here, but there are many great tutorias on the web I point you to this one:
using ngModelController it explains basics of working with ngModel and also tells bit about decorators.
When you work directly with ngModel you can set validity and state (dirty/touched/pristine) directly in your code, you can also set model value via $setViewValue().
I have written a custom directive for select component. The problem I face is simpleComboSelectionChanged() prints the previous selected value and not the current value. Please let me know what is the problem.
directive & Controller:
.directive('simpleSelect', [function($compile) {
return {
restrict: 'E',
transclude: true,
require: '^ngModel',
scope:{
id: '#',
ngModel: '=',
items: '=',
ngChange: '&'
},
// linking method
link: function(scope, element, attrs) {
scope.updateModel = function()
{
scope.ngChange();
};
},
template:'<select class="form-control" id="id" ng-model="ngModel" ng-selected="ngModel" ng-options="Type.type as Type.name | translate for Type in items"'+
'ng-change="updateModel()"></select>'
};
}])
.controller('ComboTemplateCtrl', ['$scope', function($scope) {
$scope.ComboItems = [{type:1, name:"Combo.Item1", isSet:false},
{type:2, name:"Combo.Item2", isSet:false},
{type:3, name:"Combo.Item3", isSet:false},
{type:4, name:"Combo.Item4", isSet:false},
{type:5, name:"Combo.Item5", isSet:false}
];
$scope.simpleSelectValue = $scope.ComboItems[0].type;
$scope.simpleComboSelectionChanged = function(){
console.log("Selected Item is :", $scope.simpleSelectValue);
};
}])
<simple-select id="simpleSelectTest"
ng-model="simpleSelectValue" items="ComboItems"
ng-change="simpleComboSelectionChanged()"></simple-select>
The reason this happens is because you are binding to ngModel as opposed to require: "ngModel" and using the ngModelController API to modify it. (You are, in fact, use require, but you aren't actually using. Neither are you using transclude which is not needed here).
What happens is the inner ngModel-bound variable - scope.ngModel - is changed to the currently selected item, then the inner ng-change is fired, which invokes the outer ng-change, which tries to read the outer variable bound to ng-model attribute - $scope.simpleSelectValue. But $scope.simpleSelectValue hasn't yet changed - this will happen on a $watch that will occur later.
The main point is - this is not how ngModel is meant to be used.
ngModel is a directive that custom input control authors (like yourself) can use to integrate with other ngModel-compatible directives (such as form, ng-required, ng-change and other custom directives) that can validate, transform, or just listen to changes of input values.
Since you are building a custom input control essentially (even if you are using a built-in control under the covers), you need to support your own ngModel.
Here's a conceptual overview of how this can be done:
.directive('simpleSelect',
function($compile) {
return {
restrict: 'E',
require: 'ngModel',
scope: {
id: '#',
items: '=',
},
link: function(scope, element, attrs, ngModel) {
ngModel.$render = function(){
scope.selectedValue = ngModel.$viewValue;
};
scope.onChange = function(){
ngModel.$setViewValue(scope.selectedValue);
};
},
template: '<select class="form-control" id="id" ng-model="selectedValue"' +
'ng-options="Type.type as Type.name for Type in items"' +
'ng-change="onChange()">' +
'</select>'
};
});
ngModel.$render is fired when rendering is required (for example, when model value changes) - all that you need to do here is to set the value bound to inner ng-model - no need even to do direct DOM manipulation.
ngModel.$setViewValue is called when the input directive receives input from the user. Again, since you are using an existing input directive <select>, then you could just rely on its ng-change.
You will also notice that there is no longer a need to have your own ng-change. That is the value of ngModel support - a consumer of your directive can just treat your input control like any other, include support for ng-change.
Read more about custom input controls.
I want to encapsulate my form fields in a directive so I can simply do this:
<div ng-form='myForm'>
<my-input name='Email' type='email' label='Email Address' placeholder="Enter email" ng-model='model.email' required='false'></my-input>
</div>
How do I access the myForm in my directive so I can do validation checks, e.g. myForm.Email.$valid?
To access the FormController in a directive:
require: '^form',
Then it will be available as the 4th argument to your link function:
link: function(scope, element, attrs, formCtrl) {
console.log(formCtrl);
}
fiddle
You may only need access to the NgModelController though:
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
console.log(ngModelCtrl);
}
fiddle
If you need access to both:
require: ['^form','ngModel'],
link: function(scope, element, attrs, ctrls) {
console.log(ctrls);
}
fiddle
Here a complete example (styled using Bootstrap 3.1)
It contains a form with several inputs (name, email, age, and country).
Name, email and age are directives. Country is a "regular" input.
For each input is displayed an help message when the user does not enter a correct value.
The form contains a save button which is disabled if the form contains at least one error.
<!-- index.html -->
<body ng-controller="AppCtrl">
<script>
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope) {
$scope.person = {};
});
</script>
<script src="inputName.js"></script>
<script src="InputNameCtrl.js"></script>
<!-- ... -->
<form name="myForm" class="form-horizontal" novalidate>
<div class="form-group">
<input-name ng-model='person.name' required></input-name>
</div>
<!-- ... -->
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<button class="btn btn-primary" ng-disabled="myForm.$invalid">
<span class="glyphicon glyphicon-cloud-upload"></span> Save
</button>
</div>
</div>
</form>
Person: <pre>{{person | json}}</pre>
Form $error: <pre>{{myForm.$error | json}}</pre>
<p>Is the form valid?: {{myForm.$valid}}</p>
<p>Is name valid?: {{myForm.name.$valid}}</p>
</body>
// inputName.js
app.directive('inputName', function() {
return {
restrict: 'E',
templateUrl: 'input-name.html',
replace: false,
controller: 'InputNameCtrl',
require: ['^form', 'ngModel'],
// See Isolating the Scope of a Directive http://docs.angularjs.org/guide/directive#isolating-the-scope-of-a-directive
scope: {},
link: function(scope, element, attrs, ctrls) {
scope.form = ctrls[0];
var ngModel = ctrls[1];
if (attrs.required !== undefined) {
// If attribute required exists
// ng-required takes a boolean
scope.required = true;
}
scope.$watch('name', function() {
ngModel.$setViewValue(scope.name);
});
}
};
});
// inputNameCtrl
app.controller('InputNameCtrl', ['$scope', function($scope) {
}]);
Edit 2: I'll leave my answer, as it might be helpful for other reasons, but the other answer from Mark Rajcok is what I originally wanted to do, but failed to get to work. Apparently the parent controller here would be form, not ngForm.
You can pass it in using an attribute on your directive, although that will get rather verbose.
Example
Here's a working, simplified jsFiddle.
Code
HTML:
<div ng-form="myForm">
<my-input form="myForm"></my-input>
</div>
Essential parts of the directive:
app.directive('myInput', function() {
return {
scope: {
form: '='
},
link: function(scope, element, attrs) {
console.log(scope.form);
}
};
});
What's happening
We've asked Angular to bind the scope value named in the form attribute to our isolated scope, by using an '='.
Doing it this way decouples the actual form from the input directive.
Note: I tried using require: "^ngForm", but the ngForm directive does not define a controller, and cannot be used in that manner (which is too bad).
All that being said, I think this is a very verbose and messy way to handle this. You might be better off adding a new directive to the form element, and use require to access that item. I'll see if I can put something together.
Edit: Using a parent directive
OK, here's the best I could figure out using a parent directive, I'll explain more in a second:
Working jsFiddle using parent directive
HTML:
<div ng-app="myApp">
<div ng-form="theForm">
<my-form form="theForm">
<my-input></my-input>
</my-form>
</div>
</div>
JS (partial):
app.directive('myForm', function() {
return {
restrict: 'E',
scope: {
form: '='
},
controller: ['$scope', function($scope) {
this.getForm = function() {
return $scope.form;
}
}]
}
});
app.directive('myInput', function() {
return {
require: '^myForm',
link: function(scope, element, attrs, myForm) {
console.log(myForm.getForm());
}
};
});
This stores the form in the parent directive scope (myForm), and allows child directives to access it by requiring the parent form (require: '^myForm'), and accessing the directive's controller in the linking function (myForm.getForm()).
Benefits:
You only need to identify the form in one place
You can use your parent controller to house common code
Negatives:
You need an extra node
You need to put the form name in twice
What I'd prefer
I was trying to get it to work using an attribute on the form element. If this worked, you'd only have to add the directive to the same element as ngForm.
However, I was getting some weird behavior with the scope, where the myFormName variable would be visible within $scope, but would be undefined when I tried to access it. That one has me confused.
Starting with AngularJS 1.5.0, there is much cleaner solution for this (as opposed to using the link function directly). If you want to access a form's FormController in your subcomponent's directive controller, you can simply slap the require attribute on the directive, like so:
return {
restrict : 'EA',
require : {
form : '^'
},
controller : MyDirectiveController,
controllerAs : 'vm',
bindToController : true,
...
};
Next, you'll be able to access it in your template or directive controller like you would any other scope variable, e.g.:
function MyDirectiveController() {
var vm = this;
console.log('Is the form valid? - %s', vm.form.$valid);
}
Note that for this to work, you also need to have the bindToController: true attribute set on your directive. See the documentation for $compile and this question for more information.
Relevant parts from the documentation:
require
Require another directive and inject its controller as the fourth argument to the linking function. The require property can be a string, an array or an object:
If the require property is an object and bindToController is truthy, then the required controllers are bound to the controller using the keys of the require property. If the name of the required controller is the same as the local name (the key), the name can be omitted. For example, {parentDir: '^parentDir'} is equivalent to {parentDir: '^'}.
Made your 'What I'd prefer' fiddle work!
For some reason you could see the "$scope.ngForm" string in a console.log, but logging it directly didn't work, resulting in undefined.
However, you can get it if you pass attributes to the controller function.
app.directive('myForm', function() {
return {
restrict: 'A',
controller: ['$scope','$element','$attrs', function($scope,$element,$attrs) {
this.getForm = function() {
return $scope[$attrs['ngForm']];
}
}]
}
});
http://jsfiddle.net/vZ6MD/20/