Monitor for $valid on input element in AngularJS directive - javascript

I have this directive that prepends an <i> element before each <input> element with add-icon-element attribute. Now what i'm trying to do is to observe validation for each <input> element, so when the user types something in one of them the class of the <i> element that precedes will change to fa-check green-i. I tried to do it by using attrs.$observe to see when the class changes from ng-invalid to ng-valid but it fires only one time when the DOM is structured, it doesn't react to changes in the input element.
What am i doing wrong? is there a way to do it using the input $valid?
I saw some answers regarding one input with suggestions to add names to the form and input - but what can I do if I have multiple inputs that I need to validate and not just one?
angular.module('mean.theme')
.directive("addIconElement", function () {
return {
restrict: 'EA',
link: function (scope, inputElement, attrs) {
var $icon = $('<i class="fa"></i>');
inputElement.before($icon);
attrs.$observe('class', function(val){
if (val.indexOf("ng-valid") >= 0) {
inputElement.prev().addClass('fa-check green-i');
}
});
}
};
});
and this is one of my 'inputs' in the html:
<form role="form" name="createProjectForm" class="form-validation">
<div class="form-group">
<label class="control-label text-center center-block" for="project.name">
Name Your Project
</label>
<div class="input-icon right">
<input type="text" placeholder="type here" name="project.name"
ng-model="project.name" required="required" class="form-control" add-icon-element/>
</div>
</div>
<form>

You don't need create a directive for such case, you could achieve this by using ng-class directive, only change your field name from name="project.name" to name="project_name"
<div class="input-icon right">
<input type="text" placeholder="type here" name="project.name"
ng-class="{'fa-check green-i': createProjectForm.project_name.$valid}"
ng-model="project.name" required="required"
class="form-control"/>
</div>
Update
To make it generic way, you need to require ngModel directive on that element, which will give you access to the ngModelController.
angular.module('mean.theme')
.directive("addIconElement", function () {
return {
restrict: 'EA',
require: 'ngModel', //require to get access to `ngModelController`
link: function (scope, inputElement, attrs, ngModelCtrl) {
var $icon = $('<i class="fa"></i>');
inputElement.before($icon);
scope.$watch(function(){
return ngModelCtrl.$valid; //made watch on field validation.
}, function(val){
if (val.indexOf("ng-valid") >= 0) {
inputElement.prev().addClass('fa-check green-i');
}
});
}
};
});

I think this will do it for you:
https://stackoverflow.com/a/23871934/1636157
angular.module('mean.theme')
.directive("addIconElement", function () {
return {
restrict: 'EA',
require: '^form',
link: function (scope, inputElement, attrs, ctrl) {
var $icon = $('<i class="fa"></i>');
inputElement.before($icon);
scope.$watch(ctrl.$name + '.' + inputElement.attr('name') + '.$valid', function (valid) {
if(valid) {
inputElement.prev().addClass('fa-check green-i');
} else {
inputElement.prev().removeClass('fa-check green-i');
}
});
}
};
});

Related

Reload a directive, to iterate over the elements that use it. in Angular.js

I have a directive that validates all the fields of a form that contain the "validate" directive. In all fields the word "logan" must exist to be correct. At any given time I want and need, when I click on the "Re_Evaluate all the form" button, validate if the form is correct, iterating in all the elements of the form, just like when the $timeout function is executed the first time .
app.directive('validate', function ($timeout,$compile) {
return {
restrict: 'AE',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
if (!ngModel){
return;
}
$timeout(function() {
console.log(ngModel.$viewValue); //I need this, iterate in all elements with the directive 'validate', when re_evaluate() is clicked
if(ngModel.$viewValue=='logan'){
ngModel.$setValidity('validate',true);
}
else{
ngModel.$setValidity('validate',false);
}
})
scope.re_evaluate = function(){
console.log('I need re-evaluate all the form!')
}
}
};
});
http://jsfiddle.net/hmk0yg42/
The HTML is
<section ng-app="app">
<article ng-controller="appCtrl" class="container">
<form class="form-horizontal well" name="zform" invalidate>
<input type="text" name="mytext1" ng-model="my_text" validate />
<input type="text" name="mytext2" ng-model="my_text2" validate />
<input type="text" name="mytext3" ng-model="my_text3" validate />
<button class="btn btn-lg btn-primary btn-block" type="submit" ng-disabled="zform.$invalid">
Validate</button>
</form>
</article>
</section>
And the JS is
var app = angular.module('app', []);
app.controller('appCtrl', function ($scope) {
$scope.my_text='logan';
$scope.my_text2 = 'logan';
$scope.my_text3 = 'logan';
});
app.directive('validate', function () {
return {
restrict: 'AE',
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$parsers.unshift(function(value) {
var valid = value == 'logan';
ngModelCtrl.$setValidity('logan',valid);
return value;
});
ngModelCtrl.$formatters.unshift(function(value) {
var valid = value == 'logan';
ngModelCtrl.$setValidity('logan', valid);
return value;
});
}
};
});
you don't need a re-evaluate button

Angularjs directive $compile causing ng-click to fire twice

I created an angular validator module for form validation without the need to include ngMessages in the form and everything is working as expected.
However I discovered a bug which I have been trying to fix. It has something to do with $compile.
So I have a directive that adds attributes to form elements and this is achieved by using $compile service however, it seems the $compile service causes an unwanted behaviour to ng-click so when ng-clicklocated inside the form is called it fires twice;
Here is the directive and what I am doing:
angular.module('app',[])
.directive('validateForm',['$compile',
function($compile)
{
var addErrors = function(rules, form, ctrls, scope, config){
//code
};
return {
restrict: 'A',
require: ['^form'],
link: {
post: function(scope, element, attrs, ctrls){
var form = ctrls[0];
var config = scope.validator;
if(typeof config != 'object') return;
var rules = config['rules'];
var errors = [];
//-----
},
pre: function(scope, element, attrs, ctrls){
var elm = element.find('select, input, textarea').attr('validate-field','');
element.removeAttr("validate-form"); //remove the attribute to avoid indefinite loop
element.removeAttr("data-validate-form");
$compile(element.contents())(scope);
}
}
};
}
])
.controller('UserController',['$scope', function($scope){
$scope.title = 'Form Validator';
$scope.clickThings = function(value){
alert(value); //pops up twice means ng-click fires twice
}
}]);
Form markup:
<div ng-controller="UserController">
<form novalidate="" validate-form name="form" role="form">
<div class="form-group">
<input type="text" class="form-control" ng-model="first_name" name="first_name" />
</div>
<div class="form-group">
<input type="text" class="form-control" ng-model="last_name" name="last_name" />
</div>
<div class="form-group">
<input type="text" class="form-control" ng-model="email" name="email" />
</div>
<div class="form-group">
<input type="password" class="form-control" ng-model="password" name="password" />
</div>
<div class="form-group">
<input type="text" class="form-control" ng-model="country" name="country" />
</div>
<a type="button" class="btn btn-success" ng-click="clickThings('Submit Clicked')">Submit</a>
</form>
</div>
I have created a plunker:
http://embed.plnkr.co/uIid4gczKxKI4rPOHqx7
After trying different things I realized ng-click which is already compile is compiled a second time when $compile(element.contents())(scope) is called.
To resolve this issue, only need to compile the altered/affected elements i.e
elem = element.find('select, input, textarea').attr('validate-field','');
by replacing $compile(element.contents())(scope) with $compile(elem)(scope);
So I ended up with this:
angular.module('app',[])
.directive('validateForm',['$compile',
function($compile)
{
var addErrors = function(rules, form, ctrls, scope, config){
//code
};
return {
restrict: 'A',
require: ['^form'],
link: {
post: function(scope, element, attrs, ctrls){
var form = ctrls[0];
var config = scope.validator;
if(typeof config != 'object') return;
var rules = config['rules'];
var errors = [];
//-----
},
pre: function(scope, element, attrs, ctrls){
var elem = element.find('select, input, textarea').attr('validate-field','');
element.removeAttr("validate-form"); //remove the attribute to avoid indefinite loop
element.removeAttr("data-validate-form");
$compile(elem)(scope);
}
}
};
}
])
.controller('UserController',['$scope', function($scope){
$scope.title = 'Form Validator';
$scope.clickThings = function(value){
alert(value); //pops up twice means ng-click fires twice
}
}]);
Working plunker here:
What is the purpose for using the $compile in a pre-link function? if u just want to do template transformation before the directive element get linked, u should put ur code in directive's compile and take out the $compile. Or if you would like to $compile your element after child elements are linked, u should put ur code in post-link. Putting $compile in pre-link will cause the child nodes of your child elements get linked twice.
Either choose:
angular.module('app',[])
.directive('validateForm',['$compile',
function($compile)
{
var addErrors = function(rules, form, ctrls, scope, config){
//code
};
return {
restrict: 'A',
require: ['^form'],
compile:function(element, attrs, ctrls){
// the code will be executed before get complied
var elm = element.find('select, input, textarea').attr('validate-field','');
return function(scope, element, attrs, ctrls){
var form = ctrls[0];
var config = scope.validator;
if(typeof config != 'object') return;
var rules = config['rules'];
var errors = [];
//-----
}
}
};
}
])
Or
angular.module('app',[])
.directive('validateForm',['$compile',
function($compile)
{
var addErrors = function(rules, form, ctrls, scope, config){
//code
};
return {
restrict: 'A',
require: ['^form'],
link: {
post: function(scope, element, attrs, ctrls){
var form = ctrls[0];
var config = scope.validator;
if(typeof config != 'object') return;
var rules = config['rules'];
var errors = [];
//-----
var elm = element.find('select, input, textarea').attr('validate-field','');
element.removeAttr("validate-form"); //remove the attribute to avoid indefinite loop
element.removeAttr("data-validate-form");
$compile(element.contents())(scope);
},
pre: function(scope, element, attrs, ctrls){
}
}
};
}
])
EDIT
Just eliminate the $compile clause will also do the trick. But these three ways have some difference.
For more information u should refer to the official document

How to pass a regex from a directive to an input AngularJs

i have this directive
angular.module('tutors.components')
.directive('formValidator', function() {
return {
restrict: 'A'
link: function (scope, element, attrs) {
function nameValidator (input) {
var regExp : "/^[a-zA-Z']*$/";
var validator : regExp.test(input);
return validator;
}
};
});
And this html
<input form-Validator
type="text"
required
name="firstName"
class="form-control"
ng-maxlength="20"
placeholder="First name"
ng-model="user.firstName"
ng-disabled="vm.isLoading"
tooltip="{{requiredMsg}}"
tooltip-placement="top"
tooltip-trigger="focus"
tooltip-enable="signupForm.firstName.$error.required && signupForm.firstName.$touched">
and i want to run the directive so it validates the text entered in the input, i have tried using an ng-pattern like this ng-pattern="/[a-zA-Z]+/" but that didn't work (i still prefer using it over the directive)
Your directive should be be having small case form-validator to get call to the directive by compiler.
Additionally you need to change your nameValidator function code to below.
.directive('formValidator', function() {
return {
restrict: 'A',
require: 'ngModel', //to get ngModelController
link: function(scope, element, attrs, ngModel) {
function nameValidator(input) {
var regExp = "/^[a-zA-Z]$/";
var validator = regExp.test(input);
return validator;
}
ngModel.$validators.push(nameValidator); //should pushed to validators to validate
}
}
})
OR ng-pattern should be fine, only it should have ^(start) & $(end)
ng-pattern="/^[a-zA-Z]*$/"
Demo Plunkr

$dirty set value on focus

I am creating a form in AngularJS and I want validate the fields, the problem is that the required message appear only if write something in the input and after I delete it, but I want that the message appear after to focus the input
my code is the following
<input type="text" name="textInput" data-ng-model="field.data" class="form-control" required/>
<span ng-show="form.textInput.$dirty && form.textInput.$error.required">Required!</span>
Try the following
<span ng-show="form.textInput.$touched && form.textInput.$error.required">Required!</span>
This will show the message after you touched and left(lost focus) with the field invalid. Documentation
Yes, it's doable. First add a directive like this:
myApp.directive('trackFocus', ['$timeout', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
scope: true,
link: function ($scope, element, attr, ctrl) {
element.on("focus", function() {
$timeout(function() {
ctrl.hasFocus = true;
});
});
element.on("blur", function() {
$timeout(function() {
ctrl.hasFocus = false;
});
});
}
}
}]);
Then use the directive and modify your code:
<input type="text" name="textInput" ng-model="field.data" class="form-control" required track-focus />
<span ng-show="form.textInput.hasFocus && form.textInput.$dirty && form.textInput.$error.required">Required!</span>

How to track behavior of ngModel array item using .directive

Hi everyone I'm use angularjs not so long time ago and now I have one issue related with this framework that i can't to solve. So the problem in next: I have few input fields that generate via ng-repeat:
<div class="form-group" ng-repeat="(i, name) in name_list track by $index">
<div class="row">
<div class="col-xs-12">
<input class="form-control" type="text" ng-model="data.name_list[i]" add-input/>
</div>
</div>
Where name_list some array with data. As result I have generated input fields. Next that i wanted to do it's adding new input field if all previously fields was $dirty for this thing i wrote next angular code:
userApp.directive('addInput', ['$compile', '$sce', function ($compile, $sce) {
return {
restrict: 'A',
require: '?ngModel',
link: function (scope, element, attrs, ngModel) {
scope.inputCounter = scope.name_list.length;
scope.$watch(
function(){
return ngModel.$dirty
},
function(dirty_val){
if (dirty_val){
scope.name_list.push(ngModel.$modelValue);
}
}
);
}
}}]);
but of course this code works wrong (it add new field if at last one field is $dirty) I know why it works wrong but I do not know how to track all ng-models separate, I don't know how to get access to some model like ngModel[1],so I hope somebody will help me in this, thank's
You could add a parent directive which will collect the dirty elements, and will add new element once it detects all of the other elements are dirty:
Check this plunker.
HTML:
<div collect-input>
<div class="form-group" ng-repeat="(i, name) in name_list track by $index">
<div class="row">
<div class="col-xs-12">
<input class="form-control" type="text" ng-model="data.name_list[i]" add-input/>
</div>
</div>
</div>
Once addInput detects it is dirty, call parent directive controller:
if (dirty)
collectInput.reportInput();
JS:
directive('collectInput', function() {
return {
restrict: 'A',
controller: function($scope) {
var dirtyCount = 0;
this.reportInput = function() {
var count = $scope.name_list.length;
dirtyCount++;
if (count === dirtyCount) {
$scope.name_list.push('aaa');
}
}
},
}
}).
directive('addInput', ['$compile', '$sce', function ($compile, $sce) {
return {
restrict: 'A',
require: ['^collectInput', '?ngModel'],
link: function (scope, element, attrs, ctrls) {
var collectInput = ctrls[0]
var ngModel = ctrls[1];
scope.inputCounter = scope.name_list.length;
scope.$watch(
function(){
return ngModel.$dirty
},
function(dirty_val){
if (dirty_val){
collectInput.reportInput();
}
}
);
}
}}]);

Categories