Using a var in this Angular Directive to setValidity - angularjs

I cannot figure out how to use a variable for the following case.
In html I have the following in a loop:
<span ng-show='myForm." + ids[i] + ".$error.missingInfo'>Wrong!</span>";
The generated html is correct, meaning ids[i] makes the appropriate html, like this:
<span ng-show='myForm.foo.$error.missingInfo'>Wrong!</span>";
I have an input element that has uses a custom validation directive:
<input name="me-foo" id="foo-me" validateI />
In the directive, I want to set the validity of "myForm.foo.$error.missingInfo", so my directive:
app.directive('validateI', function(){
return{
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
var id= attr.id;
var x= id.indexOf("-");
//theId will be 'foo'
var theId= id.substring(x+1);
if(viewValue.length > 0) {
//this does not work
scope.myForm.theId.$setValidity("missingInfo", true);
//as a test, I hard-coded this and it worked:
scope.myForm.foo.$setValidity("missingInfo", true);
}
}
}
else{
console.log("*** summary is empty");
}
});
}
}
});
Is there a way to use a variable in this case, or how else would I get the 'foo' error message when the element tied to this directive is not named 'foo'?

Simply use the following to make it work:
ctrl.$setValidity("missingInfo", true);

Instead of using dot notation, use this notation:
scope.myForm[id].$setValidity("missingInfo", false);

Related

Angular filter not working

I am coding a filter that will format phone numbers in a contact form I've built however for some reason the value in the input is never being updated and I'm not sure what I'm doing wrong.
Here's my HTML:
<div class='form-group'>
<input name='phone' ng-model='home.contact.phone' placeholder='(Area code) Phone' required ng-bind='home.contact.phone | phone' />
</div>
and here's my filter:
(function () {
'use strict'
angular
.module('Allay.phoneFilter', [])
.filter('phone', function () {
return function (phone) {
if(!phone) return '';
var res = phone + '::' // Since this isn't working, I'm doing something super simple, adding a double colon to the end of the phone number.
return res;
}
});
})();
I'm not sure if you need this, but here's the controller:
(function () {
'use strict'
angular
.module('Allay', [
'Allay.phoneFilter'
])
.controller('HomeController', function () {
var home = this;
});
})();
If I add an alert(res) before 'return res' in the filter I see the value I expect '123::', however the value in the input it's self is still just 123.
You need create directive to change your ngModel, like this:
.directive('phoneFormat', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var setvalue = function() {
elem.val(ctrl.$modelValue + "::");
};
ctrl.$parsers.push(function(v) {
return v.replace(/::/, '');
})
ctrl.$render = function() {
setvalue();
}
elem.bind('change', function() {
setvalue();
})
}
};
});
Use in html:
<input name='phone' ng-model='contact.phone' placeholder='(Area code) Phone' required phone-format />
JS Fiddle: http://jsfiddle.net/57czd36L/1/
Your usage of ngBind on the input is not quite correct. From the documentation,
The ngBind attribute tells Angular to replace the text content of the specified HTML element with the value of a given expression, and to update the text content when the value of that expression changes
You do not need to replace the text content of the <input> element, that wouldn't make sense. You can instead extend the formatter pipeline of the NgModelController using a directive like
app.directive('phoneFormat', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
ngModel.$formatters.push(function (value) {
if (value)
return value + '::';
});
}
}
});
Then, in your HTML,
<input ng-model='home.contact.phone' phone-format />
In case you wanted to keep the filter you wrote (for other usages), you can actually re-use it in the directive like
app.directive('phoneFormat', [ '$filter', function ($filter) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
ngModel.$formatters.push($filter('phone'));
}
}
}]);
$filter('phone') simply returns the filter function registered under 'phone'. Here is a Plunker.
Note, this solution will only format data when you change the $modelValue of the NgModelController, for example like
$scope.$apply('home.contact.phone = "123-456-7890"');
If you are looking for something to update/format the value of the input as the user is typing, this is a more complicated task. I recommend using something like angular-ui/ui-mask.
Although a filter module is a good approach, I use an 'A' directive to do the dirty work because changing the element value will affect its ng-model.
However, I would only suggest this kind of solution if your actual data manipulation could sum in 3-4 lines of code; otherwise, a more thorough approach is needed.
This is an example that will delete anything which isn't an integer:
(function () {
'use strict'
angular.module('Allay').directive('phoneValidator', function () {
return {
restrict: 'A',
link: function(scope, element, attrs) {
angular.element(element).on('keyup', function() {
element.val(element.val().replace(/[^0-9\.]/, ''));
});
}
}
});
})();
And than in your HTML template :
<input name="phone" ng-model="home.contact.phone" placeholder="(Area code) Phone" phoneValidator required/>`
You should remove your "ng-bind" cause you are filtering it and what is presented is what in the ng-model. use value instead.
<input name='phone' ng-model='home.contact.phone | phone' value="{{contact.phone | phone}}" />
see working example: JsFiddle

AngularJS Accessing Aliased Controller Attribute From Directive

OK this is a contrived example, but...
Say I have a controller like this:
app.controller('TestCtrl', function() {
this.testString;
this.otherString;
});
And I have a template like this:
<div ng-controller='TestCtrl as test'>
<input demo type='text' ng-model='test.testString'>
{{test.otherString}}
</div>
And then I have a directive like this:
app.directive('demo', function() {
return {
require:'ngModel',
link: function(scope, elem, attrs, ctrl) {
scope.$watch(attrs.ngModel, function(newVal) {
/* How do I get otherString without knowing the controller alias?
This works but is not good practice */
scope.test.otherString = newVal + ' is cool!';
/* This doesn't work, but would if the property was in scope
instead of the controller */
scope[attrs.demo] = newVal + ' is cool!';
});
}
}
});
How do I get otherString without knowing the controller alias? I could just break apart attrs.ngModel to get it, but is there an 'angular' way to get the property?
EDIT
While this example didn't exactly reflect the issues I was having with my real scenario, I did find out how to get the controller's property in the link function, allowing me to update the model:
link: function(scope, elem, attrs, ctrl) {
var otherString = scope.$eval(attrs.demo);
scope.$watch(attrs.ngModel, function(newVal) {
otherString = newVal + ' is cool!';
}
}
A directive should have zero knowledge of anything outside of itself. If the directive depends on an outside controller having defined some arbitrary property, things will break very easily.
Defining a "scope" property on the directive lets you expose an explicit API for binding data to the directive.
myModule.directive('demo', function() {
return {
scope: {
demoString: '=demo',
},
link: function(scope, element, attrs) {
// You can access demoString here, or in a directive controller.
console.log(scope.demoString);
}
};
});
And the template
<div ng-controller='TestCtrl as test'>
<input demo="test.otherString" ng-model='test.testString'>
{{test.otherString}}
</div>
This isn't the only way to facilitate passing data or setting up bindings to a directive, but it is the most common way and should cover the majority of use-cases.
If you are trying to be more angular-like I would just use the $scope in the controller and pass that to the directive like so:
app.directive('demo', function() {
return {
scope: {strings: '='},
link: function(scope, elem, attrs, ctrl) {
scope.$watch('strings.test', function(newVal) {
/* How do I get otherString without knowing the controller alias? */
scope.strings.other = newVal + ' is cool!';
});
}
}
});
then in the html:
<div ng-controller='TestCtrl as test'>
<input demo type='text' strings="strings" ng-model="strings.test" />
{{strings.other}}
</div>
In the controller you would assign:
$scope.strings = {
test: '',
other: ''
}

Compile custom directives

I'm trying to add dynamically a custom validation directive inside other custom directive. It works fine for system angular directive like "required", but not work for custom validate directive.
I have directive 'controlInput' with input, on which i dynamically add directive 'testValidation' (in real application in dependance from data of control-input).
<control-input control-data='var1'></control-input>
Directives:
app.directive('controlInput', function ($compile) {
return {
restrict: 'E',
replace: true,
template: '<div><input type="text" ng-model="var1"></div>',
link: function (scope, elem, attrs) {
var input = elem.find('input');
input.attr('required', true);
input.attr('test-validation', true);
$compile(elem.contents())(scope);
}
};
});
app.directive('testValidation', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
ctrl.$parsers.unshift(function (value) {
if (value) {
var valid = value.match(/^test$/);
ctrl.$setValidity('invalidTest', valid);
}
return valid ? value : undefined;
});
}
};
});
Full example http://plnkr.co/edit/FylMfTugHrotEMSQyTfT?p=preview
In this example I also add simple input to be sure 'testValidation' directive is working.
Thanks for any answers!
EDIT:
I suggest you fix your original program by changing the template in the controlInput directive to:
template: '<div><input type="text" testdir required ng-model="var1"></div>'
I don't see why not do it as mentioned above, but another way would be to replace the input with a new compiled one:
input.replaceWith($compile(elem.html())(scope));
NOTE:
Change
var valid = value.match(/^test$/);
To
var valid = /^test$/.test(value);
From MDN:
String.prototype.match()
Return value
array An Array containing the matched results or null if there were no
matches.
RegExp.prototype.test() returns what you need, a boolean value.

Why does the ngModelCtrl.$valid not update?

I'm trying to create a directive that contains an inputfield with a ng-model and knows if the inputcontrol is valid. (I want to change a class on a label within the directive based on this state.) I want to use the ngModelController.$valid to check this, but it always returns true.
formcontroller.$valid or formcontroller.inputfieldname.$valid do work as exprected, but since im trying to build a reusable component using a formcontroller is not very handy because then i have to determine what field of the form corresponds with the current directive.
I dont understand why one works and one doesnt, because in de angular source it seems to be the same code that should manage these states: The ngModelController.$setValidity function.
I created a test directive that contains a numeric field with required and a min value. As you can see in the fiddle below, the model controller is only triggered during page load and after that never changes.
jsfiddle with example directive
Directive code:
angular.module('ui.directives', []).directive('textboxValid',
function() {
return {
restrict: 'E',
require: ['ngModel', '^form'],
scope: {
ngModel: '='
},
template: '<input type="number" required name="somefield" min="3" ng-model="ngModel" /> '+
'<br>Form controller $valid: {{formfieldvalid}} <br> ' +
'Model controller $valid: {{modelvalid}} <br>'+
'Form controller $valid: {{formvalid}} <br>',
link: function (scope, element, attrs, controllers) {
var ngModelCtrl = controllers[0];
var formCtrl = controllers[1];
function modelvalid(){
return ngModelCtrl.$valid;
}
function formvalid(){
return formCtrl.$valid;
}
scope.$watch(formvalid, function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
});
scope.$watch(modelvalid, function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
//This one only gets triggered on pageload
alert('modelvalid ' + newVal );
});
}
};
}
);
Can someone help me understand this behaviour?
I think because you're watching a function and the $watch is only execute when this function is called !!
Watch the model instead like that :
scope.$watch('ngModel', function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
//This one triggered each time the model changes
alert('modelvalid ' + ngModelCtrl.$valid );
});
I figured it out..
The textboxValid directive has a ng-model directive, and so does the input that gets created by the directive template. However, these are two different directives, both with their own seperate controller.
So, i changed my solution to use an attribute directive like below. This works as expected.
.directive('attributetest',
function() {
return {
restrict: 'A',
require: 'ngModel',
scope: {
ngModel: '='
},
link: function (scope, element, attrs, ngModelCtrl) {
function modelvalid(){
return ngModelCtrl.$valid;
}
scope.$watch(modelvalid, function(newVal,oldVal){
console.log('scope.modelvalid = ' + ngModelCtrl.$valid );
});
}
};
});

How to access directive parameter in Angular JS?

I'm pretty new to Angular and I have run into a slight problem. I have this simple datepicker directive which works
app.directive('datepicker', function() {
return {
require: 'ngModel',
link: function(scope, elem, attr, ngModel){
$(elem).datepicker({
onSelect: function(dateText){
scope.$apply(function(){
ngModel.$setViewValue(dateText);
});
}
});
}
}
});
And the html I use to call it is
<input datepicker data-ng-model="day.date" readonly/>
I would like to be able to change the onSelect function called by the datepicker – hopefully with something like this.
<input datepicker="myOnSelectMethod()" data-ng-model="day.date" readonly/>
Then the directive would look something like this
app.directive('datepicker', function() {
return {
require: 'ngModel',
link: function(scope, elem, attr, ngModel){
if(myOnSelectMethod is defined){ //how do I access myOnSelectMethod here?
$(elem).datepicker({
onSelect: myOnSelectMethod();
});
}
else{ //proceed as before
$(elem).datepicker({
onSelect: function(dateText){
scope.$apply(function(){
ngModel.$setViewValue(dateText);
});
}
});
}
}
}
});
So my question is: how do I access the new onSelect function I want to execute from my link function?
Looking through the docs and other SO questions this seems like it should be possible but I haven't been able to make it work. I've come up with an ugly workaround by using an ng-click to activate the datepicker on a given element, but I would like to learn how to make this work if possible. Thanks!
You can check this way:
if( attr["datepicker"] == "myOnSelectMethod" &&
typeof myOnSelectMethod === "function" ){
// ...
}
Or even:
if( typeof scope[attr["datepicker"]] === "function" ){ // Instead of `scope`
// ... // may be any other object,
} // `window` for example

Resources