So, I need a validator that will check to make sure my password_confirmation field matches my password field. I ended up with the following directive:
Directive
#app.directive 'matches', ->
require: 'ngModel', #Needed for validation bits
scope: { matched_value: '=matches' } #Looks up the value of the scope element we're matching against and keeps it bound
link: (scope, elem, attrs, ctrl) ->
ctrl.$parsers.unshift (view_value) -> #Add a new parser that updates the validity
ctrl.$setValidity(elem.attr('name'), view_value == scope.matched_value)
Form
<form name="form">
<input ng-model="new_user.password" name="password">
<input ng-model="password_confirmation" name="password_confirmation" matches="new_user.password">
</form>
This works fine when the user uses the form from top to bottom. However, if they go on to change the password after they've filled in the password_confirmation then it doesn't become invalid like it should.
My first stab looked added a $watcher to the matches directive, but I can't seem to get the correct value of the new password_confirmation input.
Directive w/ Watcher (CoffeeScript)
#app.directive 'matches', ->
require: 'ngModel',
scope: { matched_value: '=matches' }
link: (scope, elem, attrs, ctrl) ->
ctrl.$parsers.unshift (view_value) ->
ctrl.$setValidity(elem.attr('name'), view_value == scope.matched_value)
scope.$watch attrs['watches'], ->
ctrl.$setValidity(elem.attr('name'), ctrl.$view_value == scope.matched_value)
But ctrl.$view_value is always undefined, leading me to believe that I'm doing this wrong.
How would I get the actual value? Is this the correct way to set up this sort of inverse relationship? Is there a better way to do this?
https://github.com/wongatech/angular-confirm-field is a good project for this.
Example here http://wongatech.github.io/angular-confirm-field/
The code below shows 2 input fields with the implemented functionality
<input ng-model="email" name="my-email" />
<input ng-confirm-field ng-model="emailconfirm" confirm-against="email" name="my-email-confirm"/>
This directive differs from other implementations in the way it uses $parsers and $watch to ensure the model property is only updated when the view value is valid.
You can watch both elements from the one directive. Pass a function to watch. Each digest, it will compare values instead of only when the one element changes.
.directive('match', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
match: '='
},
link: function(scope, elem, attrs, ctrl) {
scope.$watch(function() {
var modelValue = ctrl.$modelValue || ctrl.$$invalidModelValue;
return (ctrl.$pristine && angular.isUndefined(modelValue)) || scope.match === modelValue;
}, function(currentValue) {
ctrl.$setValidity('match', currentValue);
});
}
};
});
With (ctrl.$pristine && angular.isUndefined(ctrl.$modelValue)), it will not add the error until the field is touched or something is in it (pre-filled).
More info: https://github.com/TheSharpieOne/angular-input-match
Demo: http://jsfiddle.net/TheSharpieOne/Wnv8u/
For version 1.3.x of angular, the new validation pipeline works best, here is the version for angular 1.3.x
.directive('match', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
match: '='
},
link: function(scope, elem, attrs, ctrl) {
scope.$watch('match', function(pass){
ctrl.$validate();
});
ctrl.$validators.match = function(modelValue){
return (ctrl.$pristine && (angular.isUndefined(modelValue) || modelValue === "")) || modelValue === scope.match;
};
}
};
});
Related
I am trying to add an input directive in order to trim all text inputs. So far this is the code of my directive:
app.directive("input", function directive() {
return {
restrict: "E",
priority: 1,
require: "ngModel",
link: function link(scope, element, attrs, ctrl) {
element.on("focusout", function triggerChange(event) {
var input = event.target;
if (input.value && input.type === "text") {
ctrl.$setViewValue(input.value.trim());
ctrl.$render();
}
});
}
};
});
My issue is that the ngModel does not seem to be injected, as I get the error:
Error: [$compile:ctreq] Controller 'ngModel', required by directive 'input', can't be found!
Any idea why this happens, and how to fix it?
Update:
Actually, this is the interaction of Kendo Grid and AngularJS. The input I am testing is generated by Kendo Grid. The code of the column is standard:
{ field: "name", title: "titleName" }
You must have some input element in your HTML which does not have ng-model.
You can change your code to require: "?ngModel", and later check if ctrl is undefined or not, like:
app.directive("input", function directive() {
return {
restrict: "E",
priority: 1,
require: "?ngModel",
link: function link(scope, element, attrs, ctrl) {
if (!ctrl) { return ;}
element.on("focusout", function triggerChange(event) {
var input = event.target;
if (input.value && input.type === "text") {
ctrl.$setViewValue(input.value.trim());
ctrl.$render();
}
});
}
};
You should have provided ng-model in your html when you use this directive because you wrote require: 'ngModel', in the directive. so in your case your directive name is input so it will be something like
<input ng-model="something"> </input>
My answer is not perfect, but it is the best I could find:
app.directive("input", function directive() {
return {
restrict: "E",
priority: 1,
require: "ngModel",
link: function link(scope, element, attrs, ctrl) {
element.on("focusout", function triggerChange(event) {
var input = $(event.target);
input.val(input.val().trim());
input.trigger("change");
});
}
};
});
So basically, we trim the input, and use input.trigger("change") to inform the system that the input has changed.
A warning though, it does not work with our validation system (valdr).
I'm new to AngularJS and still learning.
I'm trying to create a custom directive and I would like to have some conditions where requires some conditions before the customrequired kick-in. For example checkbox is ticked. Below is my sample directive and HTML. Thanks and really appreciate your response. Please let me know if you still need some information.
.directive('customrequired', function () {
return {
require: '?ngModel',
link: function (scope, elm, attr, ctrl) {
if (!ctrl) return;
//conditional approach like, requires checkbox to be checked.?
}
};
})
HTML
<input type="text" ng-model="Create.ProductName" name="ProductName" customrequired="Create.IsRequired == 1"/>
<input type="checbox" ng-model="Create.IsRequired" name="Required" />
You can do put watch on your directive value and put your logic in only if its true.
.directive('customrequired', function () {
return {
require: '?ngModel',
link: function (scope, elm, attr, ctrl) {
scope.$watch(attr.customrequired,function(value){
if(value){
//conditional approach like, requires checkbox to be checked.?
}
})
}
};
})
I have a re-usable directive where the use of a custom validator is optional.
What's a good way to apply this validator attribute conditionally?
<input type="text"
name="{{name}}"
require-items="{{requireitems}}"
mongoose-error>
The require-items is an optional validation I want to pass to my directive.
The directive would be called like this:
//with validator enabled
<my-directive requireitems="{{items.length}}"></my-directive>
//no validator
<my-directive></my-directive>
Inside your my-directive directive link function:
link: function(scope, iElement, iAttrs, controller) {
if(iAttrs.requireItems) {
// checks if require-items is present
// process your validation here
}
}
Check how it's done in ng-required, using attributes and observing the same to have dynamic changes also handled.
var requiredDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
attr.required = true; // force truthy in case we are on non input element
ctrl.$validators.required = function(modelValue, viewValue) {
return !attr.required || !ctrl.$isEmpty(viewValue);
};
attr.$observe('required', function() {
ctrl.$validate();
});
}
};
};
GitHub Link
Say I have the following directive:
myApp.directive('myDirective', function() {
return {
restrict: 'A',
require: 'ngModel',
scope: {
ngModel: '='
},
link: function(scope, elem, attrs, ngModelCtrl) {
scope.$watch('ngModel', function() {
ngModelCtrl.$modelValue = 'foo';
});
}
}
});
And the following html:
<input ng-model="name" my-directive></input>
Basically, whenever the user changes the input, my-directive would ideally change the internal model value to "foo" while leaving the view value untouched.
But when I print out $scope.name in the corresponding controller, it doesn't log "foo", it logs whatever the user entered in.
It would seem that ngModelCtrl.$modelValue is not what the controller is accessing -- am I approaching this problem incorrectly?
(Also watching the ngModel in the scope feels really wrong, but I'm not sure of any other way. Any suggestions would be much appreciated!)
If you are looking for view change, you should never register a watch. ngModelController's $viewChangeListeners are specifically designed for this purpose and to avoid creating any additional watch on the ngModel. You can also remove 2 way binding set up on the ngModel.
I can think of this way.
.directive('myDirective', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs, ngModelCtrl) {
/*Register a viewchange listener*/
ngModelCtrl.$viewChangeListeners.push(function(){
/*Set model value differently based on the viewvalue entered*/
$parse(attrs.ngModel).assign(scope, ngModelCtrl.$viewValue.split(','));
});
}
}
});
Demo
While thinking about it the other way around (Credits #Cody) it becomes more concise and appropriate while using a $parser.
ngModelCtrl.$parsers.push(function(val) { return val.split(',') });
I'm hoping this isn't a duplicate - plenty of similar questions about but I can't find an answer that works.
I have an Angular directive, thus:
app.directive('emailInput', function(){
return {
restrict: 'E',
templateUrl: 'template.html',
link: function(scope, elem, attrs, ctrl){
elem.bind('keyup', function(){
// TODO - what?
})
}
}
}
and in the template html:
<input type="email" required ng-model="emailAddress" />
Without knowing the name of the form, inside the link function, I want to know the value of the emailAddress.$valid property - how can I get this?
You can require the formController which would give you access to all of the inputs registered to the form
app.directive('emailInput', function(){
return {
require: '^form', // We look for it on a parent, since it will be defined somewhere higher on the DOM.
restrict: 'E',
templateUrl: 'template.html',
link: function(scope, elem, attrs, ctrl){
elem.bind('keyup', function(){
ctrl.emailAddress.$valid //check validity
})
}
}
}
Remember that Angular keeps track of input elements by name. So you have to give your input a name attribute
<input type="email" required ng-model="emailAddress" name="emailAddress" />
I would also recommend possibly just passing all of this through a directive attribute. You probably don't want to hard code the field names. So you could just have an attribute that takes the validity
inputIsValid='formName.emailAddress.$valid'
And evaluate (or $watch it) in your directive.
We can check validity more easily without knowing the name of input elements.
app.directive('emailInput', function(){
return {
require: '^form', // We look for it on a parent, since it will be defined somewhere higher on the DOM.
restrict: 'E',
templateUrl: 'template.html',
link: function(scope, elem, attrs, ctrl){
elem.bind('keyup', function(){
ctrl.$valid //check validity here
})
}
}
}
This is an old post but for the people who get here by googling, this is the cleanest way to check validity of an input in your directive without knowing its name, so you can use your directive on any input element.
You just need to require the ngModelcontroller:
app.directive('emailInput', function(){
return {
require: 'ngModel'
restrict: 'E',
templateUrl: 'template.html',
link: function(scope, elem, attrs, ngModelCtrl){
elem.bind('keyup', function(){
ngModelCtrl.$valid //check validity
})
}
}
}
See the AngularJS document for ngModel.NgModelController, $valid under the Properties section:
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
Know it's an old thread but if someone runs into this problem this is how I solved it:
app.directive('emailInput', function(){
return {
restrict: 'E',
templateUrl: 'template.html',
controller:function($scope){
$scope.isInvalid = function(){
return $scope.myelem.data().$ngModelController.$invalid;
}
},
link: function(scope, elem, attrs){
$scope.myelem = $(elem).find("input");
}
}
}
Here is a directive that will set dirty to true even if nothing has been typed in.
By default $dirty is set if something is typed in and wouldn't show a required error message until the user submits. With this
function() {
return function (scope, element, attrs) {
$(element).blur(function () {
scope.$apply(function() {
var field = scope.$eval(attrs.makeDirty);
field.$dirty = true;
});
});
};
HTML:
<label class="fieldLabel" for="confirmEmail">Confirm Email*</label>
<input type="text" id="confirmEmail" name="confirmEmail" ng-model="confirmEmail" ng-pattern="{{Ctrl.Model.Email.EmailAddress}}" required make-dirty="form.confirmEmail">
<span class="error" ng-show="form.confirmEmail.$error.pattern && form.confirmEmail.$dirty">Emails must match</span>
<span class="error" ng-show="form.confirmEmail.$error.required && (form.$submitted || form.confirmEmail.$dirty)">Confirm your email</span>
That allows me to give feedback as the user is filling out or tabbing on the form.
Let me give you another way to do it, it can be useful in some cases
link: function (scope, element, attrs, formCtrl) {
scope.fileSizeError=false;
scope.$watch(function () {
return formCtrl.fileP.$error.maxSize;
},function(newValue) {
scope.fileSizeError=newValue;
});
}
In my case I was inside a directive that is used to upload a file so I needed to know the state of the var $error.maxSize in the template so I did in that way.