Angularjs: validation not working when control is based on directive - angularjs

Being rather new to Angularjs, I am creating textbox-label combinations in Angularjs using directives. It's working very well, but I can't get validation to work. Here is a stripped-down example.
The Html:
<form name="form" novalidate ng-app="myapp">
<input type="text" name="myfield" ng-model="myfield" required />{{myfield}}
<span ng-show="form.myfield.$error.required">ERROR MSG WORKING</span>
<br>
<div mydirective FIELD="myfield2" />
</form>
The Javascript:
var myapp = angular.module('myapp', []);
myapp.directive('mydirective', function () {
return {
restrict: 'A',
scope: { ngModel: '=' },
template: '<input type="text" name="FIELD" ng-model="FIELD" />{{FIELD}}
<span ng-show="form.FIELD.$error.required">ERROR MSG NOT WORKING</span>'
};
});
The hard coded input - myfield - works, the other - myfield2 - doesn't (the binding does, just not the required-error message).
How do I tell the ng-show attribute to sort of "replace" FIELD in form.FIELD.$error.required by myfield2?
Here is a jsFiddle.

The problem is that your directive creates a new scope for the directive, this new scope does not have access to the form object in the parent scope.
I came up with two solutions, though I suspect there is a more elegant "Angular" way to do this:
Passing down the form object
Your view becomes:
<div mydirective FIELD="myfield2" form="form" />
And the scope definition object:
return {
restrict: 'A',
scope: {
ngModel: '=',
form: '='
},
template: '<input type="text" name="FIELD" ng-model="FIELD" required/>{{FIELD}}<span ng-show="form.FIELD.$error.required">ERROR MSG NOT WORKING</span>'
};
I've updated the fiddle with this code: http://jsfiddle.net/pTapw/4/
Using a controller
return {
restrict: 'A',
controller: function($scope){
$scope.form = $scope.$parent.form;
},
scope: {
ngModel: '='
},
template: '<input type="text" name="FIELD" ng-model="FIELD" required/>{{FIELD}}<span ng-show="form.FIELD.$error.required">ERROR MSG NOT WORKING</span>'
};

Related

AngularJS custom form component / directive using ng-model

Angular custom form component / directive and $dirty property
When using regular input, such as
<form name="myForm">
<input type="text" ng-model="foobar">
</form>
after typing in the input box myForm.$dirty is true.
I'd like to create a simple directive such as
angular.module('myModule', [])
.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
fooBar: '='
},
template: '<div><button ng-click="fooBar=foo"></button><button ng-click="fooBar=bar"></button></div>'
};
});
Sample usage would be
<form name="myForm">
<my-directive foo-bar="myObj.foobarValue"></my-directive>
</form>
and after user clicks on any of the two buttons, myForm$dirty is set to true.
How is this accomplished?
Implementing custom form controls (using ngModel)
Use the ngModel controller and the object form of the require property in the DDO:
angular.module('myModule', [])
.directive('myDirective', function() {
return {
restrict: 'E',
require: { ngModelCtrl: 'ngModel' },
scope: {
ngModel: '<'
},
bindToController: true,
controllerAs: '$ctrl',
template:
`<div>
<button ng-click="$ctrl.ngModelCtrl.$setViewValue('foo')">
Set foo
</button>
<button ng-click="$ctrl.ngModelCtrl.$setViewValue('bar')">
Set bar
</button>
</div>`,
controller: function ctrl() {}
};
});
Usage:
<form name="myForm">
<input type="text" ng-model="foobar">
<my-directive ng-model="foobar"></my-directive>
</form>
By instantiating and using the ng-model controller, the directive will automatically set the form controls as necessary.
The DEMO
angular.module('myModule', [])
.directive('myDirective', function() {
return {
restrict: 'E',
require: { ngModelCtrl: 'ngModel' },
scope: {
ngModel: '<'
},
bindToController: true,
controllerAs: '$ctrl',
template:
`<div>
<button ng-click="$ctrl.ngModelCtrl.$setViewValue('foo')">
Set foo
</button>
<button ng-click="$ctrl.ngModelCtrl.$setViewValue('bar')">
Set bar
</button>
</div>`,
controller: function ctrl() {}
};
});
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="myModule">
<h2>ngModel DEMO</h2>
<form name="myForm">
<input type="text" ng-model="foobar">
<my-directive ng-model="foobar"></my-directive>
</form>
<br>myForm.$dirty = {{myForm.$dirty}}
<br>myForm.$pristine = {{myForm.$pristine}}
<br><button ng-click="myForm.$setDirty()">Set dirty</button>
<br><button ng-click="myForm.$setPristine()">Set pristine</button>
</body>
I recommend isolate scope with ngModel as an input. Output should be done with the $setViewValue method.
For more information, see
AngularJS Developer Guide - Implementing custom form controls (using ngModel)
AngularJS Developer Guide - Component-based application architecture

Not getting value of input type created through a directive in angularJS

I am using a directive for datepicker in angularJS. Below is my code for directive. It works well and it shows datepicker when I add datepicker directive in a div.now I want to use the selected date in my form when I send data in controller and from controller I pass it to php api via $http. when I check the data in php it does not shows date. Rest all fields are working fine only the field created with directive is not showing the values. I am pasting the code for directive, controller and form.
app.directive('datepicker', function () {
return {
restrict: 'A',
controller: 'datepickerCtrl',
controllerAs: 'dp',
template: '<div id="dob" ng-controller="profileCtrl" dob class="input-group date" data-date-format="dd-mm-yyyy" ><input class="from_to_input" name="1" type="text" ng-class={inputError:dp.validDate==false} class="form-control input-sm" ng-model="profile.value"/><span class="input-group-addon"><span class="glyphicon glyphicon-th"></span></span></div>',
scope: {
'value': '='
},
link: function (scope, element, attribute) {
// scope.variable = attribute.value;
}
};
});
below is code for controller for datepicker
app.controller('datepickerCtrl', function ($scope) {
$('.date').datepicker({ autoclose: true, todayHighlight: true });
var inputDate = new Date(moment($scope.value));
$scope.value = moment(inputDate).format("DD-MM-YYYY");
$('.date').datepicker('setDate', inputDate);
$scope.$watch('value', function (newVal) {
});
});
below is code for my form page
<form name="profileForm" role="form">
<input type="email" id="" name="email" ng-model="profile.email" class="login_input_area" placeholder="Email">
<div class="col-xs-2" datepicker value="date2"></div>
<input type="submit" value="Submit" ng-click="saveprofile(profile)" class="submit_btn">
</form>
My friend!
Please check online video tutorials for Angular directives!
Anyway, in your directive you have ng-model="profile.value", that should be "value" I think
second: remove ng-controller="profileCtrl" from directive template. Directives has they own local scope.
third: I prefer using restrict: "E", so you can use as custom tag, more readable on my opinion
one more: scope: { value : "="} , not scope: { 'value' : "="}
couple more: remove both controller: 'datepickerCtrl', controllerAs: 'dp'

AngularJS - Directive ngModel doesn't get updated

I have a good understanding of AngularJS Directives, but I'm thinking I'm missing something here with my directives.
What's wrong : On the initial page load, the ngModel gets triggered correctly and I successfully receive the data and it's modeled in the input.
Everything I do after... doesn't work. If I modify the text in the input, the model is not updated.
Please note that if I don't use a directive (straight HTML), it works fine.
I have a directive like so :
.directive('defaultFormGroup', function () {
return {
restrict: 'A',
templateUrl: 'form-group.html',
replace: true,
scope: {
uniqueId: '#',
labelTitle: '#',
placeholderText: '#',
ngModel: '=',
bsLabelClasses: '#',
bsFormControlClasses: '#'
}
};
})
The directive's template :
<div class="form-group">
<label class="{{bsLabelClasses}} control-label text-left" for="{{uniqueId}}">{{labelTitle}}</label>
<div class="{{bsFormControlClasses}}">
<input type="text" class="form-control" id="{{uniqueId}}" placeholder="{{placeholderText}}" data-ng-model="ngModel">
</div>
</div>
And here's how I call the directive in the template:
<div data-default-form-group
data-label-title="Name"
data-placeholder-text="Name"
data-unique-id="name"
data-bs-label-classes="col-sm-2 col-md-2"
data-bs-form-control-classes="col-sm-3 col-md-3"
data-ng-model="site.name">
</div>
I also have the same issue with radio buttons :
.directive('radioSwitch', function () {
return {
restrict: 'A',
templateUrl: 'radio-switch.html',
replace: true,
scope: {
ngModel: '=',
radioName: '#',
firstId: '#',
firstValue: '#',
firstLabel: '#',
secondId: '#',
secondValue: '#',
secondLabel: '#',
}
};
})
Template:
<input type="radio" name="{{radioName}}" value="{{firstValue}}" id="{{firstId}}" data-ng-model="ngModel">
<label for="{{firstID}}">{{firstLabel}}</label>
<input type="radio" name="{{radioName}}" value="{{secondValue}}" id="{{secondId}}" data-ng-model="ngModel">
<label for="{{secondID}}">{{secondLabel}}</label>
HTML:
<div data-radio-switch
data-ng-model="site.existingCustomer"
data-first-label="Yes"
data-second-label="No"
data-radio-name="existingCustomer"
data-first-value="True"
data-first-id="true"
data-second-value="False"
data-second-id="false">
</div>
What am I missing?
UPDATE
I was missing something in my directive... I want to choose the inputType (text, email, password, etc...)
If it's a textarea, the HTML is different... but it seems this line doesn't work data-ng-if=" inputType !== textarea "> ... if I remove it, it works
Anyone know why?
Here's a plunker : http://plnkr.co/edit/ITElF2epihSALeTutQAz
There doesn't seem to be something wrong with the directive itself: look at the example below with your directive, you can see that model which is the ng-model attribute of the directive gets updated. So, please try to reproduce your problem so we can help you (either embedded snippet, or jsfiddle, or just more code)
angular.module('test', [])
.controller('ctrl', function($scope) {
$scope.model = 'foo';
})
.directive('defaultFormGroup', function () {
return {
restrict: 'A',
template: '<div class="form-group"> <label class="{{bsLabelClasses}} control-label text-left" for="{{uniqueId}}">{{labelTitle}}</label> <div class="{{bsFormControlClasses}}"> <input type="text" class="form-control" id="{{uniqueId}}" placeholder="{{placeholderText}}" data-ng-model="ngModel"> </div> </div>',
replace: true,
scope: {
uniqueId: '#',
labelTitle: '#',
placeholderText: '#',
ngModel: '=',
bsLabelClasses: '#',
bsFormControlClasses: '#'
}
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="ctrl">
<div>model: {{model}}</div>
<div default-form-group ng-model="model"></div>
</div>
Update: it seems that the issue comes from the ng-if, as reproduced in the following snippet. It is fixed by using nh-show instead, as the ng-if may loose the binding from your directive. I am not clear about the exact cause though.
angular.module('test', [])
.controller('ctrl', function($scope) {
$scope.model = 'foo';
})
.directive('defaultFormGroup', function () {
return {
restrict: 'A',
template: '<div ng-if="true"><input type="text" data-ng-model="ngModel" /></div>',
replace: true,
scope: {
ngModel: '='
}
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="ctrl">
<div>model: {{model}}</div>
<div default-form-group ng-model="model"></div>
</div>

Create custom input directive in angular

I would like to create a custom input that looks like that:
<my-input ng-model="aScopeProperty" placeholder="Enter text"
data-title="This is my title"></my-input>
my-input should receive any property that regular input can get (like placeholder and etc...).
the output should be like this (myInputTemplate.html):
<div class="my-input">
{{title}}
<input type="text" ng-model="text" />
</div>
I created a directive:
myApp.directive('myInput', function(){
return {
restrict: 'E',
require: 'ngModel',
templateUrl: '/myInput/myInputTemplate.html',
replace: true,
scope: {
text: '=ngModel',
title: '=title'
},
}
});
the ng-model is bindded ok now,
my question is:
How can I pass the attributes (like placeholder and etc) from my-input to the inside input?
I think that I approached it the wrong way, maybe I need to do it like that:
<input my-input ng-model="aScopeProperty" placeholder="Enter text"
data-title="This is my title"></input>
and to wrap the input with:
<div class="my-input">
{{title}}
<here will be the original input>
</div>
directive call should be like
<my-input ng-model="aScopeProperty" placeholder="'Enter text'" title="'This is my title'"></my-input>
note the placeholder="'Enter text'" Enter text with in quotes ('), this indicate these values are string so angular will not search for scope variable.
and in the directive
myApp.directive('myInput', function(){
return {
restrict: 'E',
require: 'ngModel',
templateUrl: '/myInput/myInputTemplate.html',
replace: true,
scope: {
text: '=ngModel',
title: '=title',
placeholder : '=placeholder'
},
}
});
and the template
<div class="my-input">
{{title}}
<input type="text" ng-model="text" placeholder="{{ placeholder }}" />
</div>
here is the demo Plunker
You can use ng-attr as the following:
<input type="text" ng-model="text" ng-attr-placeholder="{{placeholder}}"/>
And send placeholder as attribute in your scope as the following:
scope: {
text: '=ngModel',
title: '=title',
placeholder : '=placeholder'
}
I recommend to read about ng-attr-attrName, and this useful answer.
Dynamic attributes
Read my question, and accepted answer.
The second approach succeeded!
final code:
<input my-input ng-model="aScopeProperty" placeholder="Enter text"
data-title="This is my title">
The Directive:
app.directive('myInput', function () {
return {
restrict: 'A',
scope: {
title: '=title'
},
link: function ($scope, $element) {
var wrap = angular.element('<div class="my-input-wrapper" />');
$element.addClass('form-control').removeAttr('my-input');
wrap.insertBefore($element);
$element.appendTo(wrap);
if ($scope.title) {
var title = angular.element('<span class="my-title">' + $scope.title + '</span>');
title.appendTo(wrap);
}
},
}
});
I even created my first Plunker for it, unfortunately, the Plunker don't works because it doesn't recognize: insertBefore and appendTo
http://plnkr.co/edit/XnFM75vOBg4ifHUQzGOt?p=preview

AngularJS custom directive binding to model

I am trying to build an object like:
{"age":"21","name":"foo", "address":["address1", "address2"]}
For address I am using a custom directive, I don't know how to attach this to scope (person.address). If I give ng-model="person.address" in directive, it is taking same address for both the input texts. I have to isolate scope, but don't know where to put it.
Fiddle: http://codepen.io/goutham2027/pen/EagPZG
HTML
<div ng-controller="testCtrl">
<form>
Name: <input type="text", ng-model="person.name"> <br/>
Age: <input type="text", ng-model="person.age"> <br/>
Address-1 <address> </address>
Alternate-Address <address> </address>
</form>
{{person}}
</div>
JS
app.directive('address', function() {
return {
restrict: 'E',
template: '<input type="text">'
}
})
Edit: I found out how to do it.
Fiddle: http://codepen.io/goutham2027/pen/LEjaXP
Just add an isolated scope to your directive:
app.directive('address', function() {
return {
restrict: 'E',
scope: {
ngModel: '='
},
template: '<input ng-model="ngModel" type="text">'
}
})
http://codepen.io/anon/pen/raMxJo
Try adding a person attribute to your controller.
app.controller('testCtrl', function($scope) {
$scope.person = {age:"21",name:"foo", address:["address1", "address2"]};
});
As for using your custom directive to handle the two different address fields, go with the answer from #Rasalom.
You should give the directive a private scope, like so:
scope: {
model: '='
}
Then you'll be able to pass it to your directive:
<address model="address"></address>
In your directive the address is now available in the HTML template through the private scope.
You can use isolated scope for that:
app.directive('address', function() {
return {
restrict: 'E',
scope : {person:'='},
template: '<input type="text" ng-model="person">'
}
});
<div ng-controller="ctrl">
<form>
Name: <input type="text", ng-model="person.name">
Age: <input type="text", ng-model="person.age"> <br/>
Address-1: <address person="person.address1"> </address>
Alternate-Address: <address person="person.address2"></address>
</form>
</div>
CodePen example.

Resources