Problems with angular validation in directive - javascript

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.

Related

AngularJs passing parameter values to controller

My Html:
<test-app email="hello#hotmail.com"></test-app>
My Directive:
.directive('testApp', function () {
return {
restrict: 'E',
scope: {
userEmail = '#userEmail'
},
templateUrl: 'Form.html'
};
})
My Form in another Page:
<label> Email </label>
<input type="text" id="email">
Issue: I need to get the parameter value , and print it out on the textbox itself.
What i have done: I research on a lot of articles which demonstrated on how to pass directive parameters to controller but they were very complicated.
What i think is the next step:
1) Pass the directive parameter to the controller and then bind the controller to the textbox and set the value of the textbox to be the parameter value.
2) Or is there another way i can do this?
I am stuck on how to proceed on.
You can use attribute string binding using #
In controller you initialize your userEmail variable
The directive has a user-email attribute which loads value into the directive's userEmail local scope variable.
Using userEmail:'#' allows for string binding from controller to the directive.
See demo below:
angular.module("app",[]).directive('application', function() {
return {
restrict: 'E',
scope: {
userEmail : '#'
},
templateUrl: 'Form.html'
};
}).controller("ctrl",function($scope){
$scope.userEmail="hello#hotmail.com";
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<application user-email="{{userEmail}}"></application>
<script type="text/ng-template" id="Form.html">
<label>Email</label>
<input type="text" id="email" ng-model="userEmail">
</script>
</div>

How can I manage form validity from an Angular Directive?

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>

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.

AngularJS Directive - dynamic input name binding

I am attempting to learn a little more about AngularJS' directives and have run into this situation. I would like to make a yes-no radio control that I can reuse. I have gotten most of the way - I think - but need a little push in the right direction.
I have this directive:
app
.directive('yesno', function () {
'use strict';
var config;
config = {
replace: true,
require: 'ngModel',
restrict: 'E',
scope: {
field: '=',
model: '='
},
templateUrl: 'views/yesno.html'
};
return config;
});
...and the template looks like this:
<fieldset class="yesno">
<input id="{{field}}-yes" name="{{field}}" ng-model="model" type="radio" value="yes" />
<label for="{{field}}-yes">Yes</label>
<input id="{{field}}-no" name="{{field}}" ng-model="model" type="radio" value="no" />
<label for="{{field}}-no">No</label>
</fieldset>
...and I am using it like this (simplified):
<form name="person">
<yesno field="'happy'" model="happy" />
</form>
Unfortunately what I am getting in the person object is a property {{field}} instead of happy like I would like. I keep telling myself that something like what I am attempting is possible and I just need to find it; but what.
Help please.
Update
Thank you, #HackedByChinese that helped a little but still not quite there. The problem is that I do want two way binding so that the value of the radios is populated into the parent scope; instead, when I inspect the person object it has a {{field}} property and not a happy property.
I am thinking that this is just something that AngularJS does not support in looking at:
AngularJS: Fields added dynamically are not registered on FormController
...and:
https://github.com/angular/angular.js/issues/1404
Well if you just want field to contain the string value that was entered, you can use the # prefix for the attribute to indicate it is a text binding (it will interpret the value of the attribute as literal text).
scope: {
field: '#',
model: '='
},
Click for demo.
On the other hand, if you need field to bind to the value an expression provided to the attribute (for example, you want to bind to a property on the parent scope), then you need to change the template HTML to evaluate field (simply {{field()}}) because they will be functions. The difference here is if people want to provide string values directly, they'll need to put it in quotes like your original example. I would also recommend a one-way binding, since it seems unlikely your directive would want to modify the parent scope value since it's just a name. Use the & prefix for that.
scope: {
field: '&',
model: '='
},
<fieldset class="yesno">
<input id="{{field()}}-yes" name="{{field()}}" ng-model="model" type="radio" value="yes" />
<label for="{{field()}}-yes">Yes</label>
<input id="{{field()}}-no" name="{{field()}}" ng-model="model" type="radio" value="no" />
<label for="{{field()}}-no">No</label>
</fieldset>
Click for second demo.
I ran into the same problem. The simplest solution is to inject the name value directly into the template string.
It works as long as you don't need the name value to be bound (ie. it doesn't need to change during the lifetime of the directive). Considering the way the name attribute is usually used, I think this constraint is not an problem.
app
.directive('yesno', function () {
return {
replace: true,
restrict: 'E',
scope: {
field: '#',
model: '='
},
template: function(element, attrs) {
return '<fieldset class="yesno"> \
<input id="{{field}}-yes" name="{{field}}" ng-model="model" type="radio" value="yes" /> \
<label for="{{field}}-yes">Yes</label> \
<input id="{{field}}-no" name="{{field}}" ng-model="model" type="radio" value="no" /> \
<label for="{{field}}-no">No</label> \
</fieldset>'.replace('{{field}}', attrs.field, 'g');
}
};
});
This solution is a bit messy, because of the inline html. If you want to load the template from a file as in the original question, you can do it like this:
app
.directive('yesno', ['$http', '$templateCache', '$compile',
function ($http, $templateCache, $compile) {
return {
restrict: 'E',
scope: {
field: '#',
model: '='
},
link: function(scope, element) {
$http.get('views/yesno.html', {cache:$templateCache})
.then(function(response) {
var content = angular.element(response.data.replace('{{field}}', scope.field, 'g'));
element.append(content);
$compile(content)(scope);
});
}
};
}]);

watch ng-model inside directive

I have the following directive:
directive('myInput', function() {
return {
restrict: 'AE',
scope: {
id: '#',
label: '#',
type: '#',
value: '='
},
templateUrl: 'directives/dc-input.html',
link: function(scope, element, attrs) {
scope.disabled = attrs.hasOwnProperty('disabled');
scope.required = attrs.hasOwnProperty('required');
scope.pattern = attrs.pattern || '.*';
}
};
});
with the following template:
<div class="form-group">
<label for="input-{{id}}" class="col-sm-2 control-label">{{label}}</label>
<div class="col-sm-10" ng-switch on="type">
<textarea ng-switch-when="textarea" ng-model="value" class="form-control" id="input-{{id}}" ng-disabled="disabled" ng-required="required"></textarea>
<input ng-switch-default type="{{type}}" ng-model="value" class="form-control" id="input-{{id}}" ng-disabled="disabled" ng-required="required" pattern="{{pattern}}"/>
</div>
</div>
It is used by this form:
<form ng-controller="UserDetailsCtrl" role="form" class="form-horizontal">
<div ng-show="saved" class="alert alert-success">
The user has been updated.
</div>
<my-input label="First name" value="user.firstName" id="firstName"></my-input>
<my-input label="Last name" value="user.lastName" id="lastName"></my-input>
<my-input label="Email" value="user.email" id="email" type="email" disabled></my-input>
<my-input label="Password" value="user.password" id="password" type="password"></my-input>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button ng-click="update()" class="btn btn-default">Save</button>
</div>
</div>
</form>
Which has this controller:
controller('UserDetailsCtrl', function($scope, $stateParams, User) {
$scope.user = User.get({userId: $stateParams.id});
/**
* Update the current user in this scope.
*/
$scope.update = function() {
console.log($scope.user);
$scope.user.$update({userId: $scope.user.id}).then(function(results) {
$scope.saved = true;
});
};
}).
The form is rendered fine, but when I click the Save button, the user values are never updated.
How can I use the updated values from within the myInput directive in the controller scope?
Here's the basic problem. Your ng-model is a primitive and is only being bound in one direction...it will update if parent object is changed, but since it is primitive it does not carry reference to parent object...just value. Thus updating the primitive does not update parent object that it's original value came from
Cardinal rule in angular...always have a dot in ng-model
Here's a solution that will pass the main user object to directive scope, as well as the property of that object to use for each input
<my-input id="firstName" model="user" field="firstName" label="First name"></my-input>
Now need to pass the object from controller into the directive scope:
app.directive('myInput', function() {
return {
scope: {
/* other props*/
field: '#',
model:'='/* now have reference to parent object in scope*/
},
......
};
});
Then in markup for an input will use [] notation in order to get our dot in:
<input ng-model="model[field]".../>
DEMO
In order to use angular validation you will likely have to require the ngModel controller in your directive or use nested form
Your problem is the ng-switch.
ng-switch like ng-repeat creates a new scope that inherits from the parent.
That means that if you have let's say:
$scope.foo = "hello";
And then you have something like:
<input type="text" ng-model="foo">
Inside a ng-switch. When you update foo it is going to create its own foo that hides/shadows the parent foo.
In other words, the input will show hello but when you modify it, a new foo is created hiding the parent one. That means that your parent one won't get updated (your problem).
That is not Angular.js issue, that is how Javascript works.
Normally you want to do a:
<input type="text" ng-model="foo.bar">
That way, you can play with the inheritance and instead of creating a new foo it will just update the bar on the parent.
Since that is not something you can do every time and maybe in your concrete use case you can't, the easy way is just to use $parent:
<input type="text" ng-model="$parent.value">
That way inside your ng-switch you will use directly the parent value.
I highly recommend you to read this ASAP: https://github.com/angular/angular.js/wiki/Understanding-Scopes
Example: http://plnkr.co/edit/z4D6Gk5fK7qdoh1mndzo?p=preview
Cheers.

Categories

Resources