I am trying to add an input directive in order to trim all text inputs. So far this is the code of my directive:
app.directive("input", function directive() {
return {
restrict: "E",
priority: 1,
require: "ngModel",
link: function link(scope, element, attrs, ctrl) {
element.on("focusout", function triggerChange(event) {
var input = event.target;
if (input.value && input.type === "text") {
ctrl.$setViewValue(input.value.trim());
ctrl.$render();
}
});
}
};
});
My issue is that the ngModel does not seem to be injected, as I get the error:
Error: [$compile:ctreq] Controller 'ngModel', required by directive 'input', can't be found!
Any idea why this happens, and how to fix it?
Update:
Actually, this is the interaction of Kendo Grid and AngularJS. The input I am testing is generated by Kendo Grid. The code of the column is standard:
{ field: "name", title: "titleName" }
You must have some input element in your HTML which does not have ng-model.
You can change your code to require: "?ngModel", and later check if ctrl is undefined or not, like:
app.directive("input", function directive() {
return {
restrict: "E",
priority: 1,
require: "?ngModel",
link: function link(scope, element, attrs, ctrl) {
if (!ctrl) { return ;}
element.on("focusout", function triggerChange(event) {
var input = event.target;
if (input.value && input.type === "text") {
ctrl.$setViewValue(input.value.trim());
ctrl.$render();
}
});
}
};
You should have provided ng-model in your html when you use this directive because you wrote require: 'ngModel', in the directive. so in your case your directive name is input so it will be something like
<input ng-model="something"> </input>
My answer is not perfect, but it is the best I could find:
app.directive("input", function directive() {
return {
restrict: "E",
priority: 1,
require: "ngModel",
link: function link(scope, element, attrs, ctrl) {
element.on("focusout", function triggerChange(event) {
var input = $(event.target);
input.val(input.val().trim());
input.trigger("change");
});
}
};
});
So basically, we trim the input, and use input.trigger("change") to inform the system that the input has changed.
A warning though, it does not work with our validation system (valdr).
Related
I have created a complex form element to avoid code duplication.
However I can't make it to behave same as normal input field.
HTML
<input name="first" ng-model="ctrl.first" type="text" required is-number />
<complex-input name="second" ng-model="ctrl.second" required></complex-input>
JS/ng
// main input directive
app.directive("complexInput", function(){
return {
require: "^?ngModel",
scope: {
passedModel: '=ngModel'
},
template: "<div><input ng-model='passedModel' is-number type='text' child-element /></div>",
link: function(scope, elem, attr, modelCtrl) {
angular.extend(modelCtrl.$validators, scope.childValidators || {});
}
}
});
// is number validator
app.directive('isNumber', function(){
return {
require: "^?ngModel",
link: function(scope, elem, attr, modelCtrl) {
modelCtrl.$validators.isNumber = function (modelValue, viewValue) {
var value = modelValue || viewValue;
return !isNaN(value);
};
}
}
});
// hacky directive to pass back validators from child field
app.directive('childElement', function(){
return {
require: "^?ngModel",
priority: 10,
link: function(scope, elem, attr, modelCtrl) {
if (!modelCtrl) return;
scope.childValidators = modelCtrl.$validators;
}
}
});
When I run it content of both fields errors is following.
On init:
First: {"required":true}
Second: {"required":true}
If I enter string:
First: {"isNumber":true}
Second: {**"required":true**,"isNumber":true}
If I enter number:
First: {}
Second: {}
I would expect both input and complex-inputto behave same. Problem is obviously that is-number validation on inner input is blocking model on outer complex-input so it's value is not set, unless you enter number.
What am I doing wrong?
Is there a nicer/cleaner way to do this and possibly avoid the ugly childElement directive?
Please find test plnkr here: https://plnkr.co/edit/Flw03Je1O45wpY0wf8om
UPDATE: Complex input is not a simple wrapper for input. In reality in can have multiple inputs that together compile a single value.
You can solve both problems (the childElement and the correct validation) by letting complex-element be only a wrapper around the real input field.
To do this :
The complex-element directive has to use something else than name, for example "input-name"
The input in the complex-element directive template need to use that name
You need to pass from complex-element to the input field whatever else you need (validations, events etc..)
For example, the following is your code modified and works as you expect :
var app = angular.module("myApp", []);
app.controller("MyCtrl", function($scope){
var vm = this;
vm.first = "";
vm.second = "";
});
app.directive("complexInput", function(){
return {
require: "^?ngModel",
scope: {
passedModel: '=ngModel',
name: '#inputName'
},
template: "<div><input ng-model='passedModel' name='{{name}}' is-number type='text'/ required></div>",
link: function(scope, elem, attr, modelCtrl) {
angular.extend(modelCtrl.$validators, scope.childValidators || {});
}
}
});
app.directive('isNumber', function(){
return {
require: "^?ngModel",
link: function(scope, elem, attr, modelCtrl) {
modelCtrl.$validators.isNumber = function (modelValue, viewValue) {
var value = modelValue || viewValue;
return !isNaN(value);
};
}
}
});
HTML
<p>COMPLEX:<br/><complex-input required input-name="second" ng-model="ctrl.second"></complex-input></p>
See the plunker here : https://plnkr.co/edit/R8rJr53Cdo2kWAA7zwJA
Solution was to add ng-model-options='{ allowInvalid: true }' on inner input.
This forces inner input to update model even if it's invalid.
However more nicer solution would be to pass entire model from child elements to parent directive and then iterate through their $validators.
app.directive('childElement', function(){
return {
require: "^?ngModel",
priority: 10,
link: function(scope, elem, attr, modelCtrl) {
if (!modelCtrl) return;
scope.childModels.push(modelCtrl);
}
}
});
Complex input here has two inputs that combined give a final value.
First one has to be number but it's not required, while the second one is required.
app.directive("complexInput", function(){
function preLink(scope, elem, attr, modelCtrl) {
// create local scope value
scope.inner1 = "";
scope.inner2 = "";
scope.childModels = [];
}
// do some nice mapping of inner errors
function mapKeys(index, validator) {
return validator + "." + index;
}
function postLink(scope, elem, attr, modelCtrl) {
// copy value on change to passedModel
// could be complex state
scope.$watch('inner1', function(value){
scope.passedModel = scope.inner1 + scope.inner2;
});
scope.$watch('inner2', function(value){
scope.passedModel = scope.inner1 + scope.inner2;
});
scope.childModels.forEach(function(childModel, index){
childModel.$viewChangeListeners.push(function(){
Object.keys(childModel.$validators).forEach(function(validatorKey){
modelCtrl.$setValidity(mapKeys(index, validatorKey), !childModel.$error[validatorKey]);
});
});
});
}
return {
require: "^?ngModel",
scope: {
passedModel: '=ngModel'
},
template: "<div><input ng-model='inner1' is-number type='text' child-element /><br/><input ng-model='inner2' required type='text' child-element /></div>",
compile: function(element, attributes){
return { pre: preLink, post: postLink };
}
}
});
I can't seem to find a way to update my ng-model in my directive
My directive looks like this
app.directive('DatepickerChanger', function () {
return {
restrict: 'E',
require: 'ngModel',
scope: {
Model: '=',
startDate: '=',
endDate: '=',
},
templateUrl: function(elem,attrs) {
return '/AngularJS/Directives/msDatepickerTpl.html'
},
link: function (scope, element, attrs, ngModel) {
ngModel = new Date();
}
}
});
And i use it like this
<DatepickerChanger ng-model="date" required></DatepickerChanger>
Where date is a other date object
If i console.log the ng-model, i can see that it's change it's value, but can't see it from the place where i call the directive. anyone got a clue to what im doing worng
You are using it wrong. This ngModel is a controller. Your code should look like this:
link: function (scope, element, attrs, ngModel) {
ngModel.$setViewValue(new Date());
}
Also, the directive should be called datepickerChanger and referenced in html as datepicker-changer
EDIT:
You want to update the ngModel after changing the input, for example. One way to do that is use the bind function to capture the events of the input field (I cant see your template, but I imagine that will have one input):
link: function (scope, element, attrs, ngModel) {
element.find('input').bind('change', function() {
// Do something with this.value
ngModel.$setViewValue(newDate);
});
}
I'm trying to get the value from one of the input fields in my form, but my code isn't working:
JavaScript:
angular
.module('myDirectives')
.directive('pwMatch', matchPassword);
function matchPassword() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
var modelIt = attrs.pwMatch;
var inputValue = attrs.modelIt;
console.log(inputValue);
}
};
};
HTML:
<input name="telephone" type="number" value="223344455">
<div pw-match="form.telephone"></div>
If you are trying to get the value of an input, use ng-model.
<input ng-model="form.telephone" type="number" value="223344455">
<div pw-match input-name="form.telephone"></div>
And if you want to get that value in a directive using a name on an attribute, use the$watch method on the scope.
JS
angular.module('myDirectives',[])
.directive('pwMatch', function() {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(attrs.inputName, function(value) (
var inputValue = value;
console.log(inputValue);
};
}
}
});
.module('myDirectives')
needs to be
.module('myDirectives', [])
Even though you have no dependencies, you have to have the empty array.
Also, it's a really bad idea™ to use a variable function as a directive or something, it's just going to confuse you.
This works, too, and might make your application a bit easier to maintain:
angular.module('myDirectives', [])
.directive('pwMatch', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
var modelIt = attrs.pwMatch;
var inputValue = attrs.modelIt;
console.log(inputValue);
}
}
});
I have an attribute directive that I use on an input=text tag like this:
<input type="text" ng-model="helo" my-directive />
On my directive I'm trying to use the ngModelController to save the initial value of my input, in this case the value of the ng-model associated with it.
The directive is like this:
app.directive('myDirective', function () {
return {
restrict: "A",
scope: {
},
require: "ngModel",
link: function (scope, elm, attr, ngModel) {
console.log("hi");
console.log(ngModel.$modelValue);
console.log(ngModel.$viewValue);
console.log(elm.val());
}
}
});
The problem is that ngModel.$modelValue is empty maybe because at the time the directive is initialized the ngModel wasn't yet updated with the correct value. So, how can I store on my directive the first value that is set on my input field?
How to correctly access ngModel.$modelValue so that it has the correct value?
I'll also appreciate an explanation on why this isn't working as I'm not clearly understanding this from reading the docs.
Plunkr full example: http://plnkr.co/edit/QgRieF
Use $watch in myDirective
app.directive('myDirective', function () {
return {
restrict: "A",
scope: {
},
require: "ngModel",
link: function (scope, elm, attr, ngModel) {
var unwatch = scope.$watch(function(){
return ngModel.$viewValue;
}, function(value){
if(value){
console.log("hi");
console.log(ngModel.$modelValue);
console.log(ngModel.$viewValue);
console.log(elm.val());
unwatch();
}
});
}
}
});
For Demo See This Link
I have a directive that is designed to be assigned to a normal text input.
<input type="text" ng-model="fooModel" foo-input size="30"
placeholder="insert value"></input>
I have lots of validation functions for it like testing the precision of the numbers and I use a $parsers to control the value that is submitted.
myApp.directive('fooInput', function () {
return {
restrict: 'A',
require: 'ngModel',
controller: function ($scope, $element, $attrs) {
this.errorMessage = ""
},
link: function (scope, element, attrs, ctrl)
return ctrl.$parsers.push(function (inputValue) {
var originalVal = element.val();
if (!testForOverPrecision(numericVal)) {
//do something here to set the directive as invalid
}
if (originalVal != inputValue) {
ctrl.$setViewValue(res);
ctrl.$render();
}
});
I have 2 questions:
How do I get this to work with the isValid service and do I have to have a controller scope for the error message
Is it correct for me to push the $parser inside a return statement
I am using Angular 1.2x and I created a directive to determine if the text contains the # symbol.
.directive('noAt', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
if (/#/.test(viewValue)) {
ctrl.$setValidity('noAt', false);
return undefined;
} else {
ctrl.$setValidity('noAt', true);
return viewValue;
}
});
}
};
})