I'm trying to make a hinting ajax search box with an Angular directive. I'm still begining to match data and here is what I have:
function hintSearch () {
return {
restrict: 'E',
replace: true,
template: '<div class="search_hint"><label><input type="search" ng-change="query()"></label><ul class="results"><li class="hint" ng-repeat="hint in hints | limitTo: 8" ng-bind="hint" ng-click="hint_selected(hint)"></li></ul></div>',
scope: {},
link: function(scope, element, attrs){
scope.hints = ["client1", "client2"];
scope.hint_selected = function(){
console.log("hint selected");
}
scope.query = function(){
console.log("query php");
scope.hints = ["client1", "client2", "client3"];
}
}
}
}
The problem is that the ng-change gives me an error. With ng-click or ng-keypress it works perfectly so it makes no sense! Any ideas?
This is the error it throws:
angular.js:13550 Error: [$compile:ctreq] http://errors.angularjs.org/1.5.5/$compile/ctreq?p0=ngModel&p1=ngChange
at Error (native)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js:6:412
at gb (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js:71:251)
at n (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js:66:67)
at g (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js:58:305)
at g (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js:58:322)
at n (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js:65:473)
at g (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js:58:305)
at n (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js:65:473)
at g (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js:58:305)
From error page:
This error occurs when HTML compiler tries to process a directive that
specifies the require option in a directive definition, but the
required directive controller is not present on the current DOM
element (or its ancestor element, if ^ was specified).
This is the source code of ng-change.
var ngChangeDirective = valueFn({
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
ctrl.$viewChangeListeners.push(function() {
scope.$eval(attr.ngChange);
});
}
});
ng-model is required for ng-change, there is no ng-model in your input.
<input type="search" ng-change="query()">
Add ng-model to your input, hope that will solve your problem.
<input type="search" ng-model='myModel' ng-change="query()">
Related
What i want to do is to be able to use a directive with different attributes in the same ng-app. The main goal is to run different code when the directive's input (ng-model) changes.
This is what i have now:
app.directive('customInput',
function ($compile) {
var customInputDefinitionObject = {
restrict: 'E',
replace: true,
scope: {
ident: '#'
},
template: '<input type="text" >',
controller: 'customInputController',
compile: function (tElement, tAttrs) {
$('input').removeAttr('ident')
.attr('ng-model', tAttrs.ident)
.attr('ng-change', tAttrs.ident + 'Change()');
var elemLinkFn = $compile(tElement);
return function (scope, element) {
elemLinkFn(scope, function (clone) {
element.replaceWith(clone);
})
}
}
}
return customInputDefinitionObject;
});
It works well in html e.g.:
<custom-input ident="var1"></custom-input>
<custom-input ident="var2"></custom-input>
i'm going to get to input with different ng-model and ng-change function, the controller uses dynamic names to get the $scope variables( $scope.var1Change).
The problem start when i want to use this directive inside another template.
app.directive('customInputGroup', function ($compile) {
var customInputGroupDefinitonObject = {
restrict: 'E',
replace: true,
scope: {
rident: '#',
},
template:''+
'<div>'+
'<custom-input id="first"></custom-input>'+
'<custom-input id="second"></custom-input>'+
'</div>',
controller: 'customInputGroupController',
compile: function (elem, attrs) {
$('#first', elem).removeAttr('id').attr('ident', attrs.rident + 'Start');
$('#second', elem).removeAttr('id').attr('ident', attrs.rident + 'End');
var rangeLinkFn = $compile(elem);
return function (scope, element) {
rangeLinkFn(scope, function (clone) {
element.replaceWith(clone);
})
}
}
}
return customInputGroupDefinitonObject;
});
In this case if i'm going to use it inside the HTML e.g.:
<custom-input-group rident='sg'></custom-input-group>
what i get rendered:
<div>
<input ng-model="sgEnd" ng-change="sgEndChange()">
<input ng-model="sgEnd" ng-change="sgEndChange()">
<input ng-model="sgEnd" ng-change="sgEndChange()">
</div>
For the 3rd rendered input the ng-change does not working.
If set terminal:ture in the inputGroup directive i get only to "input" rendered but both of them has the same ng-model and ng-change.
So how can i make it to render something like this:
<div>
<input ng-model="sgStart" ng-change="sgStartChange()">
<input ng-model="sgEnd" ng-change="sgEndChange()">
</div>
And if u know how would u be so nice to let me know only the "how" but the "why" aswell.
Thank you in advance.
I recently started with AngularJS, and am currently working on a simple form with a tags input field and a submit button. The input field is supposed to accept multiple tags so that on clicking submit all the tags get saved as an array.
Now I am currently using ngTagsInput directive which I found on github(http://mbenford.github.io/ngTagsInput/). This directive gives me <tags-input></tags-input> HTML element which creates an input field that accepts multiple tags before submission. Here is what it look like(look at the Tags field):
This works fine, what I need now is a directive which gives me similar functionality but instead of an element ie. <tags-input>, I want an attribute which I can include inside the conventional <input> element like <input attribute='tags-input'> .
Question:
Is there a way I can use ngTagsInput as an attribute?
Are there any other directives out there which may suit my need? If yes then please post the link in the answer.
Thanks in advance.
No. As you can see on tags-input.js file, the directive is configured as an element:
return {
restrict: 'E',
require: 'ngModel',
scope: {
tags: '=ngModel',
text: '=?',
templateScope: '=?',
tagClass: '&',
onTagAdding: '&',
onTagAdded: '&',
onInvalidTag: '&',
onTagRemoving: '&',
onTagRemoved: '&',
onTagClicked: '&',
},
But you can write your own directive with attribute type and "replace" your div element to the tags-input element.
I wrote this example:
app.directive('tagsInputAttr',
function($compile){
return {
restrict: 'A',
require: '?ngModel',
scope:{
ngModel: '='
},
link: function($scope, element, attrs, controller) {
var attrsText = '';
$.each($(element)[0].attributes, function(idx, attr) {
if (attr.nodeName === "tags-input-attr" || attr.nodeName === "ng-model")
return;
attrsText += " " + attr.nodeName + "='" + attr.nodeValue + "'";
});
var html ='<tags-input ng-model="ngModel" ' + attrsText + '></tags-input>';
e =$compile(html)($scope);
$(element).replaceWith(e);
}
};
}
);
Now you can configure your tags-input elements in two ways:
Element way:
<tags-input ng-model="tags" add-on-paste="true" display-property="text" placeholder="Add a Tag here..." ></tags-input>
Attribute way:
<input tags-input-attr ng-model="tags" add-on-paste="true" display-property="text" placeholder="Add a Tag here..." />
You can see this in action at Plunker:
https://plnkr.co/edit/yjkX22
I want to encrypt (using any algorithm) value of a ngModel. Only the $modelValue should be encrypted and view value should be plain text.
To do so, I came up with a small custom directive:-
angular.module('utilityModule').directive('encrypt', function() {
var aesUtil = new AesUtil(128, 10);
return {
restrict: 'A',
require: 'ngModel',
replace: false,
compile: function(tElem, tAttrs) {
var modelName = tAttrs['ngModel'];
var pattern = tAttrs['ngPattern']; // to check if there is ngPattern directive used.
return {
pre: function(scope, element, attrs, fn) {
// to avoid encrypting on every key press.
fn.$options = {
updateOn: 'blur'
};
fn.$parsers.push(function(value) {
//encrypt
console.log('parser invoked');
return value ? aesUtil.encrypt(modelName, modelName, modelName, value) : value;
});
fn.$formatters.push(function(value) {
//decrypt
console.log('formatter invoked');
return value ? aesUtil.decrypt(modelName, modelName, modelName, value) : value;
});
fn.$validators.pattern = function(){
// trying to overrule ngPattern directive. DOESN'T HELP!!
return true;
};
// Just for playing around
fn.$validators.amyValid = function(modelValue, viewValue) {
console.log('Custom validator invoked. modelValue=' + modelValue + ' and viewValue=' + viewValue);
return true;
};
},
post: function(scope, element, attrs, fn) {}
};
}
};
});
The directive works except when we have ngPattern used alongwith the ngModel directive. For example:-
<div class="table-responsive" ng-form="testForm">
<input name="test" type="text" ng-model="test" encrypt ng-pattern="/^[0-9]+$/"/>
<br>
{{test}}
</div>
My expectations:-
ngPattern directive should validate using the $viewValue instead of $modelValue.
How can I override the 'patternDirective' directive present in core angular.js?
Or any other suggestions...
UPDATE 1
Just realized that not just ngPattern, all other validations (maxLength, minLength, max, min) should be applied on view value only
UPDATE 2
My debugger shows that the value passed to patternDirective validator is the encrypted one. Please see the attached screenshot.
UPDATE 3
Upgrading to angularjs 1.4.5 fixed the problem. I believe that 1.3.x has validation on model value and not view value.
Upgrading to angularjs 1.4.5 fixed the problem. I believe that 1.3.x has validation on model value and not view value.
I have a directive called validate that transcludes a form and automatically validates the form based on the built in angular input validation directives. Part of this directive's job is to loop through the child inputs on the form and add appropriate tooltips for data validation. This takes place in the compile portion of the directive. The problem is that the data bindings I set in the compile function don't evaluate in html. For example
app.directive('validate', ["$timeout", "$compile", "gsap", function ($timeout, $compile, gsap) {
return {
scope: {
name: "#"
},
restrict: 'E',
replace: true,
controller: function ($scope) {
$scope.validate = {};
},
template: '<form name="{{name}}" ng-transclude></form>',
transclude: true,
compile: function compile(element, attr) {
//wrap this in a timeout function and wait for children to be available
//Have also tried this in the postLink function to the same result
$timeout(function () {
var selective = element.find('.validate');
if (selective.length > 0) {
$.each(selective, function (k, v) {
v.attr({
"tooltip": '{{validate.' + $(v).attr("name") + '}}',
"tooltip-trigger": '{{{true: "invalid", false: "valid"}[{{name}}.' + $(v).attr("name") + '.$invalid]}}'
});
});
} else {
$.each(element.find('input'), function (k, v) {
$(v).attr({
"tooltip": '{{validate.' + $(v).attr("name") + '}}',
"tooltip-trigger": '{{{true: "invalid", false: "valid"}[{{name}}.' + $(v).attr("name") + '.$invalid]}}'
});
});
}
});
return {
post: function postLink(scope, elem, attr, controller) {
//...a whole bunch of validation code, all works fine...
//should compile with attributes and resolved databindings
$compile(scope, elem, attr, controller);
}
};
}
};
}]);
This evaluates to the following in my DOM
<input ng-model="username" type="email" placeholder="Username" name="username" ng-required="true" ng-minlength="2" class="ng-pristine ng-invalid ng-invalid-required ng-valid-email ng-valid-minlength" required="required" tooltip="{{validate.username}}" tooltip-trigger="{{{true: "invalid", false: "valid"}[{{name}}.username.$invalid]}}">
As you can see, the attributes are set, but the data bindings are not evaluating as i would expect them to
Fixed it. For anyone curious, the compile function syntax is $compile(elem)(scope) I forgot the scope to compile against.
I have a validation directive called valid-number that is used to set the validity of a form using $setValidity - this works fine for any text values that I type into the input box that have the directive applied to as an attribute.
The HTML is
<form name="numberForm">
<input name="amount" type="text" ng-model="amount" required valid-number /></form>
The directive is as follow
angular.module('test',[]).directive('validNumber',function(){
return{
require: "ngModel",
link: function(scope, elm, attrs, ctrl){
var regex=/\d/;
ctrl.$parsers.unshift(function(viewValue){
var floatValue = parseFloat(viewValue);
if(regex.test(viewValue)){
ctrl.$setValidity('validNumber',true);
}
else{
ctrl.$setValidity('validNumber',false);
}
return viewValue;
});
}
};
});
However, I would also like the validation to be triggered and set the css to an invalid clsss if the value the input box is initialised to when the page is first loaded is invalid, eg if I set $scope.amount = 'not a number' I would expect the input box to have had the directive applied to it, but no joy. In order for not a number to be highlighted as invalid I have to make a change to the contents of the input, which triggers the directive.
How can I ensure the directive applies to whatever the <input> is initialised with?
A full code example is here;
http://jsfiddle.net/JW43C/5/
$parsers array contains a list of functions that will be applied to the value that model receives from the view (what user types in), and $formatters array contains the list of functions that are being applied to the model value before it's displayed in the view.
In your directive you correctly used the $parsers array, but you also need to add the $formatters array if you want the initial value to be validated:
angular.module('test',[]).directive('validNumber',function(){
return{
require: "ngModel",
link: function(scope, elm, attrs, ctrl){
var regex = /^\d$/;
var validator = function(value){
ctrl.$setValidity('validNumber', regex.test(value));
return value;
};
ctrl.$parsers.unshift(validator);
ctrl.$formatters.unshift(validator);
}
};
});
Demo plunker
You can simply call your verification function during the linking phase, like in this fiddle :
link: function(scope, elm, attrs, ctrl) {
var regex=/\d/;
var verificationFunction = function(viewValue) {
var floatValue = parseFloat(viewValue);
if(regex.test(viewValue)) {
ctrl.$setValidity('validNumber',true);
return viewValue;
}
else {
ctrl.$setValidity('validNumber',false);
return undefined;
}
};
ctrl.$parsers.unshift(verificationFunction);
verificationFunction();
}
After (>=) angular 1.3.1 version was released you could implement that behaviour with a little bit correct way, following angular validation directives style (e.g. required, maxlength).
In that case you have to append your validator as property of $validators array and there are no need in $parsers or $formatters anymore:
var app = angular.module('test', []);
app
.directive('validNumber', function() {
return {
require: "ngModel",
link: function(scope, elm, attrs, ctrl) {
var regex = /^\d+$/;
ctrl.$validators['validNumber'] = function(modelValue, viewValue) {
return regex.test(viewValue);
};
}
};
});
app.controller('NumberCtrl', NumberCtrl);
function NumberCtrl($scope) {
$scope.amount = '5z';
};
input.ng-invalid {
background-color: #FA787E;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js"></script>
<div ng-app="test">
<div ng-controller="NumberCtrl">
<div ng-form name="numberForm">
<input name="amount"
type="text"
ng-model="amount"
required
valid-number />
<span ng-show="numberForm.amount.$error.validNumber">
Doesn't look like an integer
</span>
</div>
</div>
</div>