AngularJS - Using $setValidity with ng-repeat directive - javascript

I am having a bit of a problem (mostly because of my lack of knowledge of AngularJS directives). I am using the Rating Directive directive from the UI Bootstrap library combined with my own custom validation. Multiple rating directive instances are created using ng-repeat from an object array from my controller. I use other scope variables to set the default "Rate Me" text, the custom CSS classes 'ratingOptions.ratingStates' and the max value limitiations 'ratingOptions.max'. All is working as desired... This is my directive code in the view (please note that the container form is called "categoryRatingFrom":
<div data-ng-repeat="cats in categories">
<div data-ng-form name="RatingFrom">
<div class="row no-bottom-padding">
<label class="col-sm-4 control-label">#{{ cats.name }}</label>
<div class="col-sm-8">
<div class="no-outline"
data-rating
data-ng-model="cats.value"
data-max="ratingOptions.max"
data-rating-states="ratingOptions.ratingStates"
data-on-hover="cats.onHover(value)"
data-on-leave="cats.onLeave()"
data-rating-validate > <!-- Notice the custom directive -->
</div>
#{{ cats.hoverState || cats.items[cats.value - 1].name || rateMe }}
</div>
</div>
</div>
</div>
Now I wish to set the directive instance to invalid should a value not being set, the default value is zero but the user must enter a value from 1 to ratingOptions.max (which is currently 6). I have a custom directive to validate this called ratingValidate. This is being invoked/bootstrapped and I am able to determine the current value of each directive instance, however I wish to initially set the directive/form-item to invalid and once the user selects a value we set the directive /form-item/instance to valid. This should be fairly easy but with using an ng-repeat I am unsure how to reference the specific form item. If anyone could help explain what I need to do whilst I experiment and search the AngularJS docs I would be most appreciative. This is my directive...
angular.module('myApp')
.directive('ratingValidate', function () {
// ratingValidate
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function (scope, element, attrs, ngModel) {
// do nothing if no ng-model
if (!ngModel) {
return;
}
// Listen for change events to enable binding
element.bind('click change', function () {
console.log(element[0], attrs);
if(attrs.ariaValuenow === 0){
ngModel.$setValidity('', false); // What goes here??? How do I reference the item?
} else {
ngModel.$setValidity('', false); // What goes here??? How do I reference the item?
}
});
}
};
})
;

How Angular Validation Works
Angular uses the 'name' attribute to create the $scope variables used for validation.
For example, if you have an input field with a 'required' attribute:
<form name="myform">
<input name="firstname" ng-model="firstname" type="text" required/>
</form>
Then to access the validation properties on $scope, you would do:
var validationFailed = $scope.myform.firstname.$error.required;
Where $error is an object that has 'required' as a Boolean property.
In the 'required' directive, you would see something like this:
if(attrs.value == ''){
ngModel.$setValidity('required', true); // failed validation
} else {
ngModel.$setValidity('required', false); // passed validation
}
You can pass any string to $setValidity, and it will set the $error property for you. For example, if you did:
$setValidity('test', true)
Then there would be an $error property named 'test' and it would be set to true. You can then access the property like this:
$scope.myform.firstname.$error.test
Other validation properties that are available are:
$scope.myform.firstname.$valid
$scope.myform.firstname.$invalid
$scope.myform.firstname.$pristine
$scope.myform.$valid
$scope.myform.$invalid
$scope.myform.$pristine
Hope this helps to answer your question.

To overcome this I added the following to the directive HTML:
name="rating#{{$index}}
and in the directive
ngModel.$setValidity(attrs.name, true); // or false depending on the condition

Related

How can I pass a controller's scope property to a custom directive?

Basic Setup:
I have a modal form that has a custom directive (child element) inside of it. The custom directive is a button. The modal consists of an input checkbox as well.
The End Goal:
Whenever the checkbox is 'checked', the custom directive/button element should be enabled. If the checkbox is 'unchecked', the custom directive/button should be disabled.
What I have so far AKA my thought process:
In the 'modalController' I put an ng-model on the checkbox input to dynamically change the value of a variable (isChecked). When the input is checked it sets the $scope.isChecked value to true, when it's unchecked, $scope.isChecked is false.
In order to disable the button I would pass the value of 'isChecked' from the modalController to the custom directive where its value can be put in the ng-checked expression on the button located inside the directive template (see-below).
The Problem
When I try this solution, the console log shows an error saying "inputCheck is not defined". This happens as soon as the page loads, so the console log gets printed before the user can even click the checkbox. Any ideas on how to make this work?
Modal html:
<div ng-controller= "modalController">
<input type="checkbox" ng-model="isChecked">
<button-directive inputCheck="isChecked"></button-directive>
</div>
ButtonDirective.js:
angular.module('starter').directive('buttonDirective', function ($uibModal) {
return {
restrict: "EA",
templateUrl: "app/directives/button-directive.html",
scope: {
inputCheck: "#"
},
controller: ['$scope', function ($scope) {
console.log(inputCheck);
}]
};
});
button-directive.html:
<button ng-checked="inputCheck">
You are doing it wrong. $scope variable is different from that directive scope declaration.
Every child, including directives, are within $scope scope, get it?
So your directive declaration don't need that scope, remove it.
angular.module('starter').directive('buttonDirective', function ($uibModal) {
return {
restrict: "EA",
templateUrl: "app/directives/button-directive.html"
};
});
And your modal, no need to pass your inputCheck attribute.
<div ng-controller= "modalController">
<input type="checkbox" ng-model="isChecked">
<button-directive></button-directive>
</div>
Then your directive html becomes
<button ng-checked="isChecked">
See this
https://plnkr.co/edit/icaufi3LJxTnbTOkmxdb
Working the way you did.
You were passing attribute wrong, angularjs have a bad thing IMO that is when you are passing values to a directive like that inputCheck, you must write it different with hiphen so it needs to be input-check
<button-directive input-check="isChecked"></button-directive>
And your scope declaration must use equal sign
scope: {
inputCheck: "="
},
https://plnkr.co/edit/5jlHaE0fvhoDPi2h8XGq?p=preview
You should do following changes:
<button-directive input-check="isChecked"></button-directive>
scope: {
inputCheck: "="
}
<button ng-disabled="inputCheck">

Custom validation as a function in Angular

In Angular, I am trying to validate a value of a field on blur. I have a list of customers, and I want to check if the model value in the field is in my list of customers. If not, I want to set the validity to false.
I know that ng-model-options="{updateOn: 'blur'} exists, however, I can't use this because the field is a typeahead so it must update based on the model. The validation is what needs to happen on blur.
The answer seems to be:
Write it as a function in the controller and use $setValidity just as you would in a directive. Use ng-blur to trigger the function in the input field.
-However, I keep running into examples where a custom validation (make the field invalid if the model value does not match one in the list) is only written as a directive. Are there examples of custom validation written as a function?
Write a directive that only triggers on blur.
However, I can't find examples that do either of these things.
Does anybody have an example of custom validation as a function OR a directive that only updates on blur of the field?
I found this link very helpful for custom validation but I still have the same problem with the difference between a function and a directive: How to add custom validation to an AngularJS form?
** My typeahead works, I do not need help getting uib-typeahead working but rather the accompanying custom validation
Here the example of custom validation triggered on blur:
angular.module('myApp', [])
.directive('customValidation', function() {
return {
require: 'ngModel',
link: function(scope, el, attrs, ngModel) {
el.bind('blur', function(event) {
scope.$apply(function() {
ngModel.$setValidity('customValidation', scope.name == 'test');
});
})
}
};
});
Plunk

Angularjs only triggering ng-change on input radio the first time its selected

I have made a simple example using angularjs. This example contains three radio inputs, each of which have to call a function on change (ng-change => console.log). Unfortunately, the function its only triggered the first time any radio input is selected.
HTML:
<div ng-app="myApp" ng-controller="radioGroupController">
<div ng-repeat="value in values">{{value}}
<input type="radio" name="name" ng-model="val" value="{{value}}" ng-change="logChange(val)" />
<hr/>
</div>
</div>
JS:
angular.module('myApp', [])
.controller('radioGroupController', ['$scope', function ($scope) {
$scope.values = ['Option1', 'Option2', 'Option3'];
$scope.logChange = function (newValue) {
console.log('Changed to ' + newValue);
};
}]);
Am I doing something wrong or is this the expected beheaviour?
EDIT: fiddle
Couple of issues/changes:-
1- Change value={{value}} to ng-value="value" because ng-value - Binds the given expression to the value of input[select] or input[radio], so that when the element is selected, the ngModel of that element is set to the bound value.
2- You need to set the selected value on the property on parent scope, you have ng-repeat and you really are setting value of the child scope, so each radio has its own child scope and its value never changes so ng-change is not triggered.
3- You do not need to pass the model value though logChange scope will already have it.
Try:-
In your controller add:-
$scope.selected = {};
and view set the model as a property on selected property of controller scope instead of ng-repeated child scope's val property:-
<input type="radio" name="name" ng-model="selected.val"
ng-value="value" ng-change="logChange()" />
Plnkr
You can change ng-change to ng-click, then it fires every time you actually click on the button.
Here is a JSFiddle.
Depending on what you want to achieve, consider $watch or ng-model for your purpose.
If you bind ng-model to "values" or whatever, you can check which
value it has and work with that. Angulars advantage is exactly this
two-way-databinding without doing anything. No need to call a
function to pass an argument.

AngularJS Input Directive with buttons

I am working to create a directive which will, on focus of an input field, display a set of buttons used to increment or decrement the value in the input. In this case Imperial units (1/8",1/4",1/2").
The first issue I am running into is that when a button is pressed, the focus comes off of the input and so the buttons are hidden via the directive.
The second issue is I am unsure of exactly how to access the ngModel in order to properly add (or remove) the value of the button pressed. The main reason for having the directive around the input instead of directly on the input is for formatting where I want the buttons to be placed but if that can be done when tied to the input that would certainly make this directive significantly less hard to make work correctly.
Here is the code I have setup thus far:
Form input:
<measurements>
<label>
<span>Length</span>
<i ng-show="form.length.$dirty && form.length.$error.required" class="error-icon"></i>
<input type="number" name="length" ng-model="form.length" required placeholder="Length">
</label>
</measurements>
The directive currently:
angular.module('directive.measurements', [])
.directive('measurements', [function() {
return {
restrict: 'EA',
transclude: true,
templateUrl: 'measurements.html',
scope: {
},
link: function(scope, element, attrs, ctrls) {
scope.focused = false;
var input = element.find('input');
input.on('focus', function(event) {
scope.focused = true;
scope.$apply();
});
input.on('blur', function(event) {
scope.focused = false;
scope.$apply();
});
scope.add = function(amt){
};
}
}
}]);
Finally the template 'measurements.html':
<div>
<span ng-show="focused">
<button class="button button-small" ng-click="add(.125)">+1/8</button>
<button class="button button-small" ng-click="add(.25)">+1/4</button>
<button class="button button-small" ng-click="add(.5)">+1/2</button>
</span>
<span ng-transclude>
</span>
</div>
EDIT
After playing around a little bit with the focusing issue I came up with changing the 'blur' event to the following, whether this is best or not I am not sure, but it does work:
if (event.relatedTarget === null) {
input[0].focus();
} else {
scope.focused = false;
scope.$apply();
}
Edit 2
Here is the plunker: http://plnkr.co/edit/6eUgRhUSA8yI1s42j9JD?p=preview
HERE is an improvement of your plnkr to make the directive working.
Regarding your issues:
Losing focus is handled by comparing event.targetElement with buttons from your template.
Adding value is achieved with ngModelCtrl.$setViewValue (ngModelCtrl is obtained with require).
small comment
I would not use transclusion at all -- instead would use the directive directly on the input element itself.
Give me a shout if more help needed.
UPDATE
How could this be achieved without transclusion?
In the most straightforward way you can use something like THAT or do it dynamically (look: HERE). Basically you make use of $compile service to manually add the buttons' panel to DOM.

Is it possible to add angular custom validation constraints conditionally?

The ng-required attribute is helpful to make a field required based on a condition:
<input ... ng-required="object.isRequired" \>
But how can this type of use be extended into using my own custom validation directives? For example, say I have a custom validation fieldsMatch. Now I want to add this validation to the input field based on the property "object.needsToMatch". I would hope there would be something like:
<input ... fieldsMatch="object.needsToMatch"
If the solution is to create another directive that would handle the condition, then how can I replace it inline (inside the input element)?
Secondary question: Then how to get this working when the custom validation takes an attribute, such as in the case of fieldsMatch validator, the model's field that this field needs to match with.
I would create a directive to handle this that is restricted to an attribute.
<input ... ng-model="inputModel" fieldMatchValidator fieldToMatch="object.needsToMatch" />
and in the directive:
...directive('field-match-validator', function(){
require: 'ngModel',
scope:{
fieldToMatch: '='
},
link: function(scope, element, attrs, ngModelCtrl){
//watch on the model and validate it against scope.fieldToMatch
}
});
Obviously this is somewhat crude, but should get you started.

Categories

Resources