Validating form when directive creates form inputs dynamically - angularjs

I am trying to validate form while using directive. The directive creates form inputs by compiling and replacing the element.
If I use simple template version of directive it works fine. If the input created by directive is invalid form is also invalid. It even works when created in a loop as well : http://codepen.io/kuasha/pen/gbreKP
<div ng-app="app" ng-controller="MainCtrl">
<form name="myform" ng-submit="sendForm()" novalidate>
<div class="form-group">
<myinput />
</div>
<div class="form-group">
<button ng-show="myform.$valid" type="submit">Submit!</button>
</div>
</form>
</div>
The app and controller:
var app = angular.module('app', []);
app.controller('MainCtrl', function ($scope) {
$scope.sendForm = function () {
if ($scope.myform.$valid) {
alert('form valid');
}
};
});
Here is the simple working version of the directive:
app.directive('myinput', function () {
return {
restrict: 'E',
template: '<label>Test</label><input ng-model="value" type="text" required ng-minlength="5" />'
};
});
But if I change the directive to use a link function and compile then, even when the input inside the directive is invalid, forms $valid is still true: http://codepen.io/kuasha/pen/qEZoKv?editors=101
app.directive('myinput', function ($compile) {
return {
restrict: 'E',
link: function (scope, element, attributes) {
var template = '<div><input type="text" required ng-minlength=5 /></div>'
var newElement = angular.element(template);
$compile(newElement)(scope);
element.replaceWith(newElement);
}
};
});
Edit:
Directive could create its own validator-
app.directive('myinput', function ($compile) {
return {
require:'^form',
restrict: 'E',
scope: {
},
controller: ['$scope', function($scope){
}],
link: function (scope, element, attributes, ctrl) {
var template = '<label>Test</label><input name="myctrl" ng-model="value" type="text" required ng-minlength="5" />'
var newElement = angular.element(template);
$compile(newElement)(scope);
element.replaceWith(newElement);
scope.$watch("value", function(){
ctrl.$setValidity('myctrl', scope.value && scope.value.length > 0);
console.log("Validity set");
});
}
};
});
But its probably not right approach for this particular problem.

You should switch the replaceWith and $compile (DEMO):
element.replaceWith(newElement);
$compile(newElement)(scope);
I think it is because you can't really compile an element that is not yet in the DOM. But I'm not sure.

Related

Calling callback function in directive doesn't work

I want the directive to pass a value to the callback function (that was defined in the controller). The function is never called, though, and I have no clue why.
HTML:
<input type='file' fileread success="fileUploaded(data)" name="myFile" id="myFile" />
DIRECTIVE:
myApp.directive('fileread', function() {
return {
scope: {
callBack: '&success',
},
link: function (scope, element) {
scope.callBack({data: "test"});
}
};
});
CONTROLLER:
myApp.controller('MyCtrl', function($scope){
$scope.fileUploaded = function(data){
console.log(data);
};
})
FIDDLE: http://jsfiddle.net/v6r9L6g3/4/
You are missing ng-app and ng-controller in your html
your html must be
<div ng-app="myApp" ng-controller="MyCtrl">
<input type='file' fileread success="fileUploaded(data)" name="myFile" id="myFile" />
</div>
Above will work fine
Alternatively you can use your directive as follow
myApp.directive('fileread', function() {
return {
scope: {
success: '&',
},
link: function (scope, element) {
scope.success({data: "test"});
}
};
});
And
here is working example http://jsfiddle.net/31rpuobL/
The only one trouble with execution of your code was that input have to be surrounded by div with ng-controller directive on it. Like following
<div ng-controller="MyCtrl">
<input type='file' fileread success="fileUploaded(data)" name="myFile" id="myFile" />
</div>
And everything just works perfect.

Get access to form controller validation errors in a custom directive

I have a directive that wraps a form element with some inputs. One of the options is passing in a formName. Usually, with a form with the example name of myForm, to show an error you would do something like myForm.firstName.$error.required.
But, how do I get access to the errors when the form name is dynamically being passed in to the directive?
example usage
<my-custom-form formName='myForm' formSubmit='parentCtrl.foo()'></my-custom-form>
directive
angular.module('example')
.directive('myCustomForm', [
function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'myCustomForm.directive.html',
scope: {
fornName: '#',
formSubmit: '&'
},
require: ['myCustomForm', 'form'],
link: function(scope, element, attrs, ctrls) {
var directiveCtrl = ctrls[0];
var formCtrl = ctrls[1];
scope.data = {};
scope.hasError = function(field) {
// how do i show the errors here?
};
scope.onSubmit = function() {
scope.formSubmit();
};
}
};
}]);
template
<form name="{{ formName }}" ng-submit="onSubmit()" novalidate>
<div class="form-group" ng-class="{'is-invalid': hasError('fullName') }">
<input type="text" name="fullName" ng-model="data.full_name" required />
<div ng-show="hasError('fullName')">
<p>How do I show this error?</p>
</div>
</div>
<div class="form-group" ng-class="{'is-invalid': hasError('email') }">
<input type="text" name="email" ng-model="data.email" ng-minlength="4" required />
<div ng-show="hasError('email')">
<p>How do I show this error?</p>
</div>
</div>
<button type="submit">Submit</button>
</form>
I think the only problem with your code is that the directive requires itself, I don't think that will work. Just removing the myCustomForm from the require works fine.
To check if the field has errors, you just need to check if the $error object in the form controller is empty.
require: ['form'],
link: function(scope, element, attrs, ctrls) {
var formCtrl = ctrls[0];
scope.data = {};
scope.hasError = function(field) {
// Field has errors if $error is not an empty object
return !angular.equals({}, formCtrl[field].$error);
};
Plunker

ng-disabled doesn't update even if form validity is populated

I created a directive that check if an input is valid based on some criteria. In this form I have a button that is ng-disabled="form.$invalid". The problem is that, even if it seems like the valid state is populated, my button is not enabled when my custom directive change the validity state of the input.
Here is a simple example:
<div ng-app="app">
<div ng-controller="fooController">
<form name="fooForm">
<input type="text" ng-model="foo" foo>
<input type="submit" value="send" ng-disabled="fooForm.$invalid">
</form>
</div>
</div>
JS (CoffeeScript):
app = angular.module 'app', []
app.directive 'foo', ->
restrict: 'A'
require: 'ngModel'
link: (scope, element, attrs, controller) ->
element.bind 'keyup', ->
if controller.$viewValue isnt 'foo'
controller.$setValidity 'foo', false
else
controller.$setValidity 'foo', true
app.controller 'fooController', ($scope) ->
$scope.foo = 'bar'
In short, this directive check if the input's value === 'foo'. If it's not it sets the validity 'foo' to false, otherwise to true.
Here is a jsfiddle (javascript) : http://jsfiddle.net/owwLwqbk/
I found a solution involving $apply: http://jsfiddle.net/owwLwqbk/1/
But I wonder if there's not an other, a better way of doing it? Isn't the state supposed to populate?
The jqLite event handler runs outside the context of Angular, that's why you needed the scope.$apply() before it would work.
Another option is to use a watch...
link: function(scope, element, attrs, controller) {
scope.$watch(function () {
return controller.$viewValue;
}, function (newValue) {
controller.$setValidity('foo', newValue === 'foo');
});
}
Fiddle
Please see demo below
var app;
app = angular.module('app', []);
app.directive('foo', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
ctrl.$parsers.unshift(function(val) {
console.log(val);
if (val == "bar") {
ctrl.$setValidity('foo', true);
} else {
ctrl.$setValidity('foo', false);
}
});
}
};
});
app.controller('fooController', function($scope) {
$scope.foo = 'bar';
});
.ng-invalid-foo {
outline: none;
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="fooController">
[Only "bar" is valid value] <br/>
<form name="fooForm">
<input type="text" ng-model="foo" foo="">
<input type="submit" value="send" ng-disabled="fooForm.$invalid" />
</form>
</div>
</div>

ng-model is not bind with controller's $scope when I am creating a form dynamically using directive.

view code:- mydir is my custom directive
<div ng-model="vdmodel" mydir="dataValue">
</div>
my directive :-
app.directive('mydir',['$translate',function($translate){
return {
restrict: 'A',
transclude: true,
scope: {dir:'=mydir'},
compile: function(element, attrs) {
return function(scope, element, attrs, controller){
var setTemplate = '';
var setOpt = '';
if(scope.dir.itemtype== 'NUMBER'){
setTemplate = '<input type="number" class="form-control form-font ng-animate ng-dirty"';
setTemplate +='" ng-model="dir[somevalue]" value="'+scope.sizing.somevalue+'" >';
element.html(setTemplate);
}
}
}
}
});
There are many more form element in directive, but when I am trying to submit and collect value in my controller function I get nothing.
What I am doing wrong and what is the best way to collect form values ?
there are quiet a few changes that you will need to do
1.as you are using isolate scope, pass ngModel as well to the directive
scope: {dir:'=mydir', ngModel: '='},
2.as per the best practise ngModel must always have a dot
ng-model="params.vdmodel"
3.make sure to initialize the params object in controller
$scope.params = {}
Usually, a directive would share the same scope as the parent controller but since you are defining a scope in your directive, it sets up it's own isolate scope. Now since the controller and directive have their seperate scope, you need a way to share the data between them which is now done by using data: "=" in scope.
The app code
var myApp = angular.module('myApp', []);
myApp.controller('myController', function ($scope, $http) {
$scope.vdmodel = {};
})
.directive("mydir", function () {
return {
restrict: "A",
scope:{
data:"=model",
dir:'=mydir'
},
templateUrl: 'test/form.html'
};
});
The form.html
<form>
Name : <input type="text" ng-model="data.modelName" /><br><br>
Age : <input type="number" ng-model="data.modelAge" /><br><br>
Place : <input type="text" ng-model="data.modelPlace" /><br><br>
Gender:
<input type="radio" ng-model="data.modelGender" value="male"/>Male<br>
<input type="radio" ng-model="data.modelGender" value="female"/>Female<br><br><br>
</form>
The page.html
<div ng-app="myApp" >
<div ng-controller="myController" >
<div model="vdmodel" mydir="dataValue"></div>
<h3>Display:</h3>
<div>
<div>Name : {{myData.modelName}} </div><br>
<div>Age : {{myData.modelAge}}</div><br>
<div>Place : {{myData.modelPlace}}</div><br>
<div>Gender : {{myData.modelGender}}</div><br>
</div>
</div>
</div>
You have to use $compile service to compile a template and link with the current scope before put it into the element.
.directive('mydir', function($compile) {
return {
restrict: 'A',
transclude: true,
scope: {
dir: '=mydir'
},
link: function(scope, element, attrs, controller) {
var setTemplate = '';
var setOpt = '';
if (scope.dir.itemtype == 'NUMBER') {
setTemplate = '<input type="number" class="form-control form-font ng-animate ng-dirty"';
setTemplate += '" ng-model="dir.somevalue" value="' + scope.dir.somevalue + '" >';
element.html($compile(setTemplate)(scope));
}
}
}
});
See the plunker below for the full working example.
Plunker: http://plnkr.co/edit/7i9bYmd8blPNHch5jze4?p=preview

AngularJS: Cannot access form field errors from a directive

LIVE DEMO
I use Angular 1.2.18 (have to support IE8), and I'm trying to create something similar to ngMessages that exists in Angular 1.3:
HTML:
<form name="form" novalidate>
<div>
<label for="phone">Phone:</label>
<input id="phone" name="phone" ng-model="phone" type="text"
required ng-minlength="5">
<div form-errors-for="form.phone">
<div form-error="required">Required</div>
<div form-error="minlength">Too short</div>
</div>
</div>
</form>
JS:
angular.module("Validation", [])
.directive("formErrorsFor", function() {
return {
scope: {
model: '=formErrorsFor'
},
controller: function($scope) {
this.model = $scope.model;
}
};
})
.directive("formError", function() {
return {
require: '^formErrorsFor',
link: function(scope, element, attrs, ctrl) {
console.log(ctrl.model.$error); // Always {}. Why??
}
};
});
Unfortunately, accessing form.phone.$error from the formError directive, always results in an empty object. Why it doesn't have the required and the minlength properties?
PLAYGROUND HERE
I tried you jsbin. The issue here is you are trying to access errors too early.
Also the scope on the two directives are different.
I changed you jsbin and it seems to work. I added a watch
scope.$watch(function(){
return ctrl.model.$error;
},function(n,o){
console.log(n);
});
for error changes as it s not defined on scope. See this http://jsbin.com/duxewigi/3/edit

Resources