custom validation directive in angular - javascript

I want to make a custom validation directive for my angular app. the only problem is that I dont know how to get an value
<select class="form-control" ng-model="$parent.newSector" ng-options="item.id as item.name for item in sectors" not-empty-array="merchant.sectors"></select>
As you can see from my markup i have got an directive called notEmptyArray where i set an expression (it's the same as ng-model, just a different expression). How can i now access it in my directive?
directive = function({
require: "ngModel",
link: function(scope, element, attributes, control) {
//how to access the value of merchant.sectors (its an array in the scope)
}
});

You would need to isolate the scope:
app.directive("notEmptyArray",function(){
return {
scope:{
items:"=notEmptyArray"
},
link: function(scope, element, attributes, control) {
/* anything you feed to "not-empty-array" would be accessible
inside items */
console.log(scope.items);
}
}
});

Related

Required directive controller is not present on the current DOM element

So I was wondering what actually "required directive controller is not present on the current DOM element" means
here is the link of error:https://docs.angularjs.org/error/$compile/ctreq?p0=ngModel&p1=contenteditable
You are missing the ngModel directive for your custom directive.
Try something like this:
app.directive('contenteditable', function () {
return {
restrict: 'E',
require: 'ngModel',
link: function (scope) {
// do something
}
};
});
and in your HTML file add ng-model
<contenteditable ng-model="name"></contenteditable>
From the angular.js documentation on directives
When a directive uses require, $compile will throw an error unless the specified controller is found. The ^ prefix means that this directive searches for the controller on its parents (without the ^ prefix, the directive would look for the controller on just its own element).

Add custom Directive to existing Input that already has angular directives [ng-model/ng-required]

I would like to use a standard input control that is decorated with ng-model and ng-required and then add my own custom attribute directive that provides uib-typeahead functionality to the control.
I used this link to get my directive partly working.
Add directives from directive in AngularJS
PLUNKR - The Version 2 of the directive does not work correctly with ng-model
My Directive does add typeahead functionality and that works quite well, but it is not binding the model on to the control after item is selected.
I have two version of my directive.
Version 1: is an element style directive and I have been using it successfully for a while, but it fell short when I wan't to have a bit more control over the input element, especially when I wanted to use ng-required='true' and other ng-message directives.
Version 2: is an attribute style directive, I went with this because I felt it was better to just add the typeahead functionality that I wanted to any standard HTML that can optionally use ng-required='true', ng-model etc...
While this directive is mostly working, it does not interact correctly with ng-model and I'm not sure how to get it working
angular.module(APP)
.directive('wkLocationSuggest', ['$compile', function ($compile) {
return {
restrict: 'A',
require: 'ngModel',
replace: false,
//terminal: true,
//priority: 0,
scope: {
wkApiModel: '=' // Provide access to the internal data that is returned via the API lookup
},
controller: 'LocationSuggestController',
link: function (scope, element, attrs, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
element.attr('typeahead', 'location as row.location for row in typeAhead($viewValue)');
element.attr('typeahead-wait-ms', '750');
element.attr('typeahead-on-select', 'onSelectInternal($item, $model, $label)');
element.attr('typeahead-min-length', '2');
element.attr('typeahead-focus-first', 'true');
element.removeAttr("wk-location-suggest"); //remove the location-suggest to avoid indefinite loop
element.removeAttr("data-wk-location-suggest"); //also remove the same attribute with data- prefix if it exists
// None of this is working
//// invoked when model changes from the outside
//ngModelCtrl.$render = function () {
// //scope.innerModel = ngModelCtrl.$modelValue;
//};
////// invoked when model changes from the inside
//scope.onChange = function (value) {
// ngModelCtrl.$setViewValue(scope.innerModel);
//};
scope.onSelectInternal = function ($item, $model, $label) {
// This fires, but it effects the ng-model on the first input,
// but not the input that this directive is attached too
ngModelCtrl.$setViewValue($item.location);
};
$compile(element)(scope);
}
};
}]);
These two images demonstrate part of the problem, may be better to test for yourself using PLUNKR above
I initially tried to dynamically add validators to your wk-location-suggest-new directive by implementing blur on the input element in combination with ngModel's $setValidity method; but don't know what exactly was preventing the event from firing.
Therefore, I turned to the other directive wk-location-suggest-old and tweaked it a bit to fit in both desired behaviors.
There, I noticed that you were missing a couple of things:
First of all, in order for a form element to glue with the form itself (wkProfileCompany in your case), and to work with ng-model, the element (in the directive template) needs a name.
Secondly, ng-required (or required) would work with the form only if it is added as an attribute to the element in the directive template, not the directive which compiles to the template containing the element.
Directive Definition
As you may notice, I've passed two properties from the outer scope to the directive's inner scope, namely:
the name of the input element,
and an isRequired flag as to specify whether the input is required or not.
.
.directive('wkLocationSuggestOld', [function () {
return {
restrict: 'E',
require: '?ngModel',
scope: {
name: '#', // <==
isRequired: '=' // <==
},
template: '<input name="{{name}}" type="text" class="{{innerClass}}" ng-model="innerModel"'
+ ' ng-change="onChange()" uib-typeahead="location as row.location for row in typeAhead($viewValue)" '
+ ' typeahead-wait-ms="750" typeahead-on-select="onSelectInternal($item, $model, $label)" '
+ ' typeahead-min-length="2" typeahead-focus-first="true" '
+ ' ng-required="isRequired">', // <== added ng-required here
controller: 'LocationSuggestController',
link: function (scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}
...
}])
HTML
Finally, you can use the tweaked directive in your HTML as such:
<wk-location-suggest-old class="form-control" type="text" name="location2" ng-model="location2" is-required="true"></wk-location-suggest-old>
Plunker
Update
One of the possible reasons for ng-model not correctly binding in the wk-location-suggest-new directive to a provided value (i.e. location3) is that you are replacing the whole DOM element with a new custom DOM element which is compiled with the isolated scope of the directive itself.
Since the directive wk-location-suggest-new has an isolate scope, the scope is totally unaware of location3, because location3 (and all the other location values) are defined in the scope of MainCtrl and NOT the scope of the directive itself; therefore, you'll end up binding the input's value to an undefined property.
link: function (scope, element, attrs, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
...
$compile(element)(scope); // <== here
You need to update your model in setTimout() like below as you have an isolated scope in the directive.
setTimeout(function () {
scope.$apply(function () {
scope.location3 = 'Your selected value'
});
}, 2000);
Alternatively you can also utilize $timeout service to achieve the same result.

Error 'Multiple Directive Resource Contention' when creating custom directive and using UI-bootstrap

I am trying to create a directive that has as input the rating and which plots stars in the DOM based on this input.
However, when I try the following:
.directive('stars', function (Utils) {
return {
restrict: 'AE', //E = element, A = attribute, C = class, M = comment
transclude: true,
template: "{{starsObj}}",
link: function (scope, element, attrs) {
scope.starsObj = Utils.returnStars(attrs.rating)
}
}
})
where Utils.returnStars(attrs.rating) returns an array (which I would like to use later).
... I get the following error:
Multiple Directive Resource Contention
https://docs.angularjs.org/error/$compile/multidir?p0=rating&p1=%20(module:%20ui.bootstrap.rating)&p2=stars&p3=%20(module:%20noodl.controllers-product)&p4=template&p5=%3Cspan%20ng-mouseleave%3D%22reset()%22%20rating%3D%225%22%3E
Removing ui-bootstrap from my projects resolves the issue, but I do need ui-bootstrap in the end.
What is going on?
You don't need to add 'scope.' prefix to your variables from directive scope, so you should change your template attribute of the directive to this:
return {
restrict: 'AE', //E = element, A = attribute, C = class, M = comment
transclude: true,
template: "{{starsObj}}", //You don't need 'scope.' prefix.
link: function (scope, element, attrs) {
scope.starsObj = Utils.returnStars(attrs.rating)
}
}
Demo on plunker.
UPD: But I see that your error from the console is related to ui-bootstrap. Can you provide code more code, where you use it?
UPD2: The issue here is that ui.bootstrap has his own rating directive. So, to solve this problem you can just rename your rating attribute of stars directive to something else. I updated my plunker.

Access ng-model from inside directive link function

I'm attempting to validate inputs inside a directive. Consider the following:
.directive('test', function ($parse, $http, $sce, $timeout) {
return {
restrict: 'EA',
scope: {
},
template: '<div class="holder">
<input id="A" name="inputA" ng-model="modelA" />
<input id="B" name="inputB" ng-model="modelB" />
</div>',
link: function($scope, elem, attrs) {
}
}
If I wanted to do my own custom validation inside the link function, how would I reference the "modelA" model? $scope.modelA appears to only reference the value inside the input, not the model itself (which is what I need for validation, as I understand it). All the other references I could find seem to deal with binding referencing models from the parent scope, which I don't need.
What am I missing?
I see you are using an isolated scope, I can't tell if that's part of your problem, but with an isolated scope and no properties, the parent scope outside the directive will have no way to access the values inside and vice-versa. I created a plunk to explain isolated scopes: (plnkr). If you want to access values on your parent scope you probably want something like this for your directive scope:
scope: {
modelA: "=modelA",
modelB: "=modelB"
},
And then calling your directive would work like this:
<test model-a="firstName", model-b="lastName"/>
Another issue might be that the link function only executes once for each time you use your directive as it is processing it and linking it. For instance if the value of modelA changes then your link function will not be called again. If you want to do something when modelA changes you need to set up a watcher for the change in your link function. Note that it will be be called once initially without changing the value:
link: function($scope, elem, attrs) {
$scope.$watch('modelA', function(newValue, oldValue) {
console.log('modelA changed from', oldValue, 'to', 'newValue');
});
}

Angular directives - How to select template based on attribute values?

I am developing a widget where I want to render some messages/text one after another. I want to change the template of the message based on the type of message.
my current directive setup is as follows
directive('cusMsgText', function(){
return {
restrict: 'E',
template:function(elements, attrs){
return '<div></div>';
},
link: function($scope, iElm, iAttrs, controller) {
//add children to iElm based on msg values in $scope
}
};
});
The directive is used as follows
<div ng-repeat="(key, value) in chatUser.msg">
<data-cus-msg-text msg="value.type"></data-cus-msg-text>
</div>
Now my question are -:
Is it possible to return one of multiple strings (templates) from
template function itself based on the actual value of attribute
msg. I tried accessing attrs.msg in template function and it
return value.type.
If not then, Is it good to manipulate template under linker or I
need to move it to compile function?
To render a different template based on value.type you can use the ng-switch statement:
<div ng-switch="value.type">
<div ng-switch-when="type1">
//...template for type 1 here...
</div>
<div ng-switch-when="type2">
//...template for type 2 here...
</div>
</div>
Also, if I understood your second question: manipulation of the uncompiled directive should be done in the compile function, all the manipulation which occurs after compilation should go in the link function.
Docs for ngSwitch
EDIT: +1 to Sebastian for understanding what you wanted. However, what he is proposing is essentially reinventing the wheel, since it is essentially compiling and inserting the template manually (which is what ngSwitch does for you). Also, you can access the attributes you put on your directive through the attrs argument of the link function.
In the template function you don't have access to the scope of your directive. If you want to control what gets rendered you can do this using conditional logic (e.g. ng-switch) in a global template as suggested by simoned or use a link function:
.directive('cusMsgText', function($compile) {
return {
restrict: 'E',
scope: {
msg: '=',
item: '='
},
link: function(scope, element, attrs) {
templates = {
x: '<div>template x {{item.name}}</div>',
y: '<div>template y {{item.name}}</div>'
};
var html = templates[scope.msg];
element.replaceWith($compile(html)(scope));
}
};
});

Categories

Resources