How can I manage form validity from an Angular Directive? - javascript

I have error spans on a page:
<span id="example" name="example" ng-model="example">
<span ng-show="form.example.$error.exampleError">
{{'example' | translate}}
</span>
</span>
I need to set the validity on this from a directive so I am passing the form as an attribute.
<form name="form">
<my-directive form="form"></my-directive>
</form>
Inside the directive I then set the validity to true or false.
This works, however from a design perspective I am creating a circular dependency as I have a directive inside a form and then I am passing the form to the directive, so my question really is, is there a better way to achieve this with passing the form to the directive?
I could create a service that stores the state of the form (true/false) and use ng-show, but I would prefer to use $error and $setValidity.

This article really helped me with this kind of scenario:
http://blog.thoughtram.io/angularjs/2015/01/11/exploring-angular-1.3-validators-pipeline.html
angular.module("app")
.directive('validateExample', function () {
return {
require: 'form',
link: function (scope, element, attrs, ctrl) {
if (you-want-example-to-be-valid) {
ctrl.$setValidity("example", true);
}else{
ctrl.$setValidity("example", false);
}
}
};
});
Then in your html you would do something like this:
<form name="FormName" validate-example novalidate>
<input id="example" name="example" ng-model="example"/>
</form>
<div ng-messages="FormName.$error" role="alert">
<div class="alert-danger" ng-message="example">This error will show if example is invalid according to the directive
</div>
</div>
Take note that I'm using Angular's ngMessages you can read more on it here:
https://scotch.io/tutorials/angularjs-form-validation-with-ngmessages

It depends on what your directive does.
Take a look at require property for the directive - https://docs.angularjs.org/guide/directive
One way is to use attribute directive on the form itself.
angular.module('app').directive('formDirective', function() {
return {
require: 'form',
restrict: 'A',
link: function (scope, element, attrs, formCtrl) {
//do stuff with your form using formCtrl
}
};
});
Then you do
<form name="form" form-directive>
</form>

Related

How to get parameter on ngClick. And then, triggered the directive to injected dinamically template

I need to inject new template dynamically depending on the value or parameter of radio button.
This is my HTML
<div class="container-fluid" ng-app="rjApp">
<div class="panel-body" ng-controller="mainController">
<input name="penanggung_biaya" type="radio" ng-model="checked" ng-click="broadcast(umum)" class="ace" value="umum"> <!-- I though, umum is the parameter which I want passed through to the Controller -->
<input name="penanggung_biaya" type="radio" ng-model="checked" ng-click="broadcast(instansi)" class="ace" value="instansi">
<example-directive message="message"> </example-directive>
</div>
</div>
and, this is the JS
var rjApp = angular.module('rjApp',[]);
rjApp.config(function($interpolateProvider) {
$interpolateProvider.startSymbol('{::');
$interpolateProvider.endSymbol('::}');
})
//mainCotroller, should be work by ngClick through radio button
function mainController($scope, $rootScope) {
$scope.broadcast = function(event){
console.log(event) //I've been thought, this is the part to passing the parameter of radio button, but not gonna works.
$rootScope.$broadcast('handleBroadcast');
};
}
//the Directive, should be injected dinamically template, depends on ngClick parameter inside radion button
rjApp.directive('exampleDirective', function() {
return {
restrict: 'E',
scope: {
message: '='
},
link: function(scope, elm, attrs) {
scope.$on('handleBroadcast', function(doifq) {
templateUrl= '<?php echo url("registrasi/rawat_jalan/penanggung_biaya/") ?>'+doifq //This is the part to injected the dinamically template. And I've been guess the **doifq**, is the paramter to passing by the mainController
});
},
};
});
Please, somebody help me.
Regards.
In broadcast, you could pass the parameter,
$scope.broadcast = function(event){
console.log(event);
$rootScope.$broadcast('handleBroadcast',event);
};
This way you would be getting doifq value in directive depending on the which radio button is clicked.

AngularJS dynamic required attribute in directive and form validation

I have a directive that receives whether an element should be required or not from a REST api. Right now, I can't seem to get the form to invalidate when an attribute is set to required.
So, in essence I'm able to dynamically add the 'required' attribute from the directive below, but it doesn't invalidate the form. Looking through chrome I see that, even though the required attribute exists, a required entry in the $error array doesn't exist.
app.directive('requireiftrue', function ($compile) {
return {
require: '?ngModel',
link: function (scope, el, attrs, ngModel) {
if (!ngModel) {
return;
}
if(attrs.requireiftrue==="true"){
console.log('should require');
el.attr('required', true);
$compile(el.contents())(scope);
}
else{
console.log('should not require');
}
}
};
});
Here's a jsfiddle to illustrate the problem. And, here's sample JSON returned from my rest API
{
race: false,
martialStatus: true,
}
EDIT: While the accepted answer got me up and running, I still had a good bit of finagling to do.
Namely:
1. Resolving a deferred promise to ensure that my form actually receives the required fields to validate
2. observing my 'requireiftrue' attribute
My solution
module config:
function config($stateProvider) {
$stateProvider
.state('testState', {
url: '/test/form',
controller: 'TestCtrl',
templateUrl: 'test/form/testForm.tpl.html',
resolve: {
formDefaultService: function getFormDefaults($q, dataservice) {
// Set up a promise to return
var deferred = $q.defer();
var myData = dataservice.getFormDefaults();
deferred.resolve(myData);
return deferred.promise;
//return
}
},
data: {
pageTitle: 'Test Info'
}
});
}
And, finally the directive / HTML that receives api data:
Directive:
.directive('requireiftrue', function ($compile) {
return {
require: '?ngModel',
link: function (scope, el, attrs, ngModel) {
if (!ngModel) {
return;
}
attrs.$observe('requireiftrue', function(value){
if(value==="true"){
el.attr('required', true);
el.removeAttr('requireiftrue');
$compile(el[0])(scope);
}
});
}
};
});
HTML:
<input max="40"
requireiftrue={{defaults.validation.name}}
validNumber
id="name"
name="name"
type="text"
ng-model="test.name"
class="form-control">
You had two issues:
The first is el.contents() returned an empty array. so The first thing you should do is change it to el[0]. But had el.contents() worked you would hav had a much more serious problem. You would have been trying to compile a directive that has itself as a directive which would lead to an infinite loop (well until the browser crashed any way).
So here is the revised code:
var app = angular.module('form-example', []);
app.directive('requireiftrue', function ($compile) {
return {
require: '?ngModel',
link: function (scope, el, attrs, ngModel) {
if (!ngModel) {
return;
}
if(attrs.requireiftrue==="true"){
console.log('should require');
el.attr('required', true);
el.removeAttr('requireiftrue');
$compile(el[0])(scope);
}
else{
console.log('should not require');
}
}
};
});
I should note however that now this directive is a one-off. If the model will change, the directive will not be on the element any longer to deal with it.
Instead of using a directive, use ng-init to initialize requireiftrue.
and assign this value to ng-required like ng-required="requireiftrue" as shown below. As you said you are getting the data from rest api, you can initialize requireiftrue with the value you are getting from api, instead of true or false as shown in example below.
Hope this helps you.
Updated fiddle
http://jsfiddle.net/zsrfe513/3/
<form ng-app="form-example" name='fo' class="row form-horizontal" novalidate>
<div class="control-group" ng-form="testReq">
<h3>Form invalid: {{testReq.$invalid}}</h3>
<label class="control-label" for="inputEmail">Email</label>
<div class="controls" ng-init='requireiftrue = true'>
<input id="inputEmail" placeholder="Email" ng-model="email" name='ip' ng-required='requireiftrue'>
<span ng-show="testReq.ip.$dirty && testReq.ip.$error.required">
Required.
</span>
</div>
</div>
</form>
Try:
1. adding the required directive to the input you want to apply validation to
<input id="inputEmail" class="requireiftrue" placeholder="Email"
ng-model="email" requireiftrue="true" required>
2 Defining the directive as type class and adding the directive class to the HTML input field
JS
app.directive('requireiftrue', function ($compile) {
return {
restrict: 'C',
require: '?ngModel',
.....
HTML
<input id="inputEmail" class="requireiftrue" placeholder="Email" ng-model="email" requireiftrue="true" required>
here is a update of your fiddle - http://jsfiddle.net/4fb6wg30/
You just need to add the "required" attribute to the input.
<input max="40"
requireiftrue={{defaults.validation.name}}
validNumber
id="name"
name="name"
type="text"
ng-model="test.name"
class="form-control"
required="required">
I used <input ng-required="true"> worked fine for my angular validation component.
If your using the new angular component make sure to pass in required: "#" instead of required: "="
scope: {
required: '#'
}
I also took this further and required integer and min/max validation the same way.

I want to be able to set Angulars ng-pattern inside a directive with a template and it's own scope to validate a form

This is part of a much more complicated directive that needs to have its own scope as well as require ngModel and replace the existing input. How can I have the directive add the ng-pattern attribute? As you can see in this jsfiddel the validation doesn't change based on the input if the ng-pattern is added in the template. This is because this will be added to an existing application that has a ton of different attributes already on a ton of different input elements, and I'm trying to make the addition as easy to implement as possible by just adding functionality to the existing input fields without messing up other things.
http://jsfiddle.net/MCq8V/
HTML
<div ng-app="demo" ng-init="" ng-controller="Demo">
<form name="myForm" ng-submit="onSubmit()">
<input lowercase type="text" ng-model="data" name="number">
Valid? {{myForm.number.$valid}}
<input type="submit" value="submit"/>
</form>
</div>
JS
var module = angular.module("demo", []);
module.directive('lowercase', function() {
return {
require: 'ngModel',
restrict: 'A',
scope:{},
replace: true,
link: function(scope, element, attr, ngModelCntrl) {
},
template: '<input class="something" ng-pattern="/^\d*$/">',
};
});
module.controller('Demo', Demo);
function Demo($scope) {
$scope.data = 'Some Value';
}
Thanks so much for any help! Ideally I would be able to just change something small and keep the ng-pattern, but I think I may have to do the validation setting on my own.
Here's how the pattern attribute is added to input item in a directive I have in my application. Note the use of compile at the end of the link function. In your case, rather than replace the element contents with a template, you'd just work with the existing element input tag.
link: function (scope, element, attrs, formController) {
// assigned template according to form field type
template = (scope.schema["enum"] !== undefined) &&
(scope.schema["enum"] !== null) ?
$templateCache.get("enumField.html") :
$templateCache.get("" + scope.schema.type + "Field.html");
element.html(template);
// update attributes - type, ng-required, ng-pattern, name
if (scope.schema.type === "number" || scope.schema.type === "integer") {
element.find("input").attr("type", "number");
}
element.find("input").attr("ng-required", scope.required);
if (scope.schema.pattern) {
element.find("input").attr("ng-pattern", "/" + scope.schema.pattern + "/");
}
element.find("input").attr("name", scope.field);
// compile template against current scope
return $compile(element.contents())(scope);
}
I tried quite a few things and it seemed that using a directive to replace an input with an input was tricking Angular up somewhere - so this is what I came up with:
http://jsfiddle.net/MCq8V/1/
HTML
<div ng-app="demo" ng-init="" ng-controller="Demo">
<form name="myForm" ng-submit="onSubmit()">
<div lowercase model="data"></div>
Valid? {{myForm.number.$valid}}
<input type="submit" value="submit"/>
</form>
</div>
JS
var module = angular.module("demo", []);
module.directive('lowercase', function() {
return {
restrict: 'A',
scope:{
data:'=model'
},
replace: true,
template: '<input class="something" ng-pattern="/^\\d*$/" name="number" ng-model="data" type="text">',
};
});
module.controller('Demo', Demo);
function Demo($scope) {
$scope.data = 'Some Value';
}
Also, you needed to escape your backslash in your regex with another backslash.

Problems with angular validation in directive

I am fighting with the validation in an angular directive without success.
The form.name.$error object seems to be undefined, when I submit the name property to the directive template. If i use a fixed name-attribute inside the template, the $error object is fine, but of course identical for all elements.
The html is:
<form name="form" novalidate>
<p>
<testvalidation2 name="field1" form="form" field="testfield4" required="true">
</testvalidation2>
</p>
</form>
The directive looks like this:
app.directive('testvalidation2', function(){
return {
restrict: 'E',
scope: {
ngModel: '=',
newfield: '=field',
required: '=required',
form: '='
},
templateUrl: 'template2.html',
link: function(scope, element, attr){
scope.pattern = /\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/;
scope.name = attr.name;
}
} // return
});`
and finally the template:
<div>
<input name="{{name}}" type="text" ng-model="newfield" ng-required="required" ng-pattern="pattern"> {{FIELD}}</input>
<span ng-show="form.name.$error.required">Required</span>
<span ng-show="form.name.$error.pattern"> Invalid </span>
<p>Output {{form.name.$error | json}}</p>
</div>
I have created a plunker for my Angular Validation Problem
and would be happy, if someone would help me to win the fight.
Michael
I don't have a fix for this but I can tell you what the problem is.
Firstly in your html form="form" should have name of the form form="form2".
Secondly Since you are creating a new scope in the directive, the scope created is a isolated scope which does not inherit from parent, which means that the the template input control that you add would not get added to the parent scope form2.
The only way out currently i can think of is to not use isolated scope.

Angularjs initial form validation with directives

I have a validation directive called valid-number that is used to set the validity of a form using $setValidity - this works fine for any text values that I type into the input box that have the directive applied to as an attribute.
The HTML is
<form name="numberForm">
<input name="amount" type="text" ng-model="amount" required valid-number /></form>
The directive is as follow
angular.module('test',[]).directive('validNumber',function(){
return{
require: "ngModel",
link: function(scope, elm, attrs, ctrl){
var regex=/\d/;
ctrl.$parsers.unshift(function(viewValue){
var floatValue = parseFloat(viewValue);
if(regex.test(viewValue)){
ctrl.$setValidity('validNumber',true);
}
else{
ctrl.$setValidity('validNumber',false);
}
return viewValue;
});
}
};
});
However, I would also like the validation to be triggered and set the css to an invalid clsss if the value the input box is initialised to when the page is first loaded is invalid, eg if I set $scope.amount = 'not a number' I would expect the input box to have had the directive applied to it, but no joy. In order for not a number to be highlighted as invalid I have to make a change to the contents of the input, which triggers the directive.
How can I ensure the directive applies to whatever the <input> is initialised with?
A full code example is here;
http://jsfiddle.net/JW43C/5/
$parsers array contains a list of functions that will be applied to the value that model receives from the view (what user types in), and $formatters array contains the list of functions that are being applied to the model value before it's displayed in the view.
In your directive you correctly used the $parsers array, but you also need to add the $formatters array if you want the initial value to be validated:
angular.module('test',[]).directive('validNumber',function(){
return{
require: "ngModel",
link: function(scope, elm, attrs, ctrl){
var regex = /^\d$/;
var validator = function(value){
ctrl.$setValidity('validNumber', regex.test(value));
return value;
};
ctrl.$parsers.unshift(validator);
ctrl.$formatters.unshift(validator);
}
};
});
Demo plunker
You can simply call your verification function during the linking phase, like in this fiddle :
link: function(scope, elm, attrs, ctrl) {
var regex=/\d/;
var verificationFunction = function(viewValue) {
var floatValue = parseFloat(viewValue);
if(regex.test(viewValue)) {
ctrl.$setValidity('validNumber',true);
return viewValue;
}
else {
ctrl.$setValidity('validNumber',false);
return undefined;
}
};
ctrl.$parsers.unshift(verificationFunction);
verificationFunction();
}
After (>=) angular 1.3.1 version was released you could implement that behaviour with a little bit correct way, following angular validation directives style (e.g. required, maxlength).
In that case you have to append your validator as property of $validators array and there are no need in $parsers or $formatters anymore:
var app = angular.module('test', []);
app
.directive('validNumber', function() {
return {
require: "ngModel",
link: function(scope, elm, attrs, ctrl) {
var regex = /^\d+$/;
ctrl.$validators['validNumber'] = function(modelValue, viewValue) {
return regex.test(viewValue);
};
}
};
});
app.controller('NumberCtrl', NumberCtrl);
function NumberCtrl($scope) {
$scope.amount = '5z';
};
input.ng-invalid {
background-color: #FA787E;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js"></script>
<div ng-app="test">
<div ng-controller="NumberCtrl">
<div ng-form name="numberForm">
<input name="amount"
type="text"
ng-model="amount"
required
valid-number />
<span ng-show="numberForm.amount.$error.validNumber">
Doesn't look like an integer
</span>
</div>
</div>
</div>

Categories

Resources