I'm struggling with a particular case for validation. I have to do cross-field validation with what is essentially a more complicated multi-select directive. The problem is that this is a very general directive and will not necessarily need validation in every case nor will it need the same validation.
simplifiedSelect.tpl.html
<select name="vm.name" ng-options='p in data' ng-model='vm.model'>
simplifiedSelect.js
(function() {
angular
.module('myApp')
.directive('simplifiedSelect', simplifiedSelect)
function simplifiedSelect(){
var directive = {
restrict='E',
scope:{
secondVar:'='
},
controller:Controller,
controllerAs:'vm',
templateUrl: '/simplifiedSelect.tpl.html'
}
Controller.$inject('$scope')
function Controller($scope) {
//do stuff
}
}());
Every example that I have seen seems to require that the validation directives are attached directly to the input, not to the directive itself, and attempting to attach the validation to the directive led to having no result, even when I attached and required ngModel to the outer directive. I have been able to get a basic custom validation to work on another field, but this is just beyond me.
Related
I have a bunch of forms in my project and all of them have a set of specific validation expressions. On top of that I have also a loading element attached to it so it makes pretty hard to reuse that button throughout the project. With that in mind I figured I could use a directive instead. The problem is that when I pass in my expressions through attributes it does not update/validate as user inputs data. Basically it just sits with the attribute like if it was a string and not an expression.
In a nutshell what I've been trying to achieve is something like this:
<submit-button label="My Label" validate="!myForm.$valid"></submit-button>
Which will return something like that:
<button ng-disabled="!myForm.$valid">My Label</button>
Here's a basic isolated template on jsfiddle of what I have so far: https://jsfiddle.net/lucasbittar/8m992bet/3/
Thank you so much!
The easiest way to achieve this in that version of Angular is to utilize a two-way attribute binding on your directive. Bind the validate attribute/scope property to your expression, and reference that in the ngDisabled directive.
An important thing to note is that ngDisabled (and other ng-prefixed directives, IIRC) operate on expressions directly, so you shouldn't use double-brackets to interpolate them.
Directive code becomes:
function submitButton () {
var submit = {
restrict: 'E',
require: ['^form'],
template: '<button ng-disabled="validate">{{ label }}</button>',
scope: {
validate: '='
},
link: link
}
return submit;
function link (scope, el, attr, formCtrl) {
scope.label = attr.label;
}
}
Check out the working fiddle here: https://jsfiddle.net/8m992bet/6/
Note that if you wanted, you could also mimic the above using an inherited scope on your directive combined with scope.$watch, but using a two-way binding is easier.
Also note that on Angular 1.5 you can utilize a one-way binding on a directive using < instead of =.
I'm new to JavaScript and Angular JS.
I have to write a custom directive to validate the content of an input field passed in an .aspx page; more precisely I have to check if an username with the value passed in the input already exists. In my directve I have to call a service that checks for duplicates in the db and afterwards depending on the result I have to perform a success callback/error callback and to return an object containing info about the validation check and a message to diplay. In particular I'm stuck on this last part regarding callbacks and returning an object with info (in my version I cannot use $.defer).
Unfortunaltely, I cannot take advantage of the already existing custom validation for forms provided by Angular (that is $asynchrounous validators and $q.defer) as I have to use an older version of Angular 1.2.26.
Could you provide me some hints/examples on how to achieve it? I'm trying hard but I still find custom directives a bit difficult and I cannot find examples for custom validation (except for those based on Angular 1.3 $asynch validators).
Thank you in advance
You can easily use $http service in your directive's link or controller function and do operations with response data. What is the problem exactly can you be more clear?
You can write your own directive for this: example
app.directive('asyncNameCheck', function($http){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elm, attr, ngModel){
elm.bind('input', function(){
ngModel.$setValidity('asyncValid', false);
$http.get('http://graph.facebook.com/facebook')
.success(function(data){
ngModel.$setValidity('asyncValid', true);
});
})
}
}
})
and in the html
<form name="myform">
<input name="testfield" async-name-check required ng-model="test"/>
<p ng-show="myform.testfield.$error.asyncValid">This field is invalid</p>
</form>
Just note that this directive is not a reusable one but works for your requirements. Do not do this in the controller, I've spent a lot of time with angular validation and if you're controlling validation in the controller, it will cause you alot of issues later on.
I'm a bit confused with the use of $scope in controllers and of scope in directives. Please verify if my understanding is correct (and also provide some alternative ways how to do this).
Let's say I have an html:
<div ng-controller="app1_Ctrl">
.
.
.
<input type="text" ng-model="value"/>
<input type="checkbox" />
<button ng-click="submit()"></button>
</div>
And my main.js
(function() {
angular.module('mainApp', ['app1']);
})();
And my app1 looks like this (based on official AngularJS documentation here)
(function() {
var app = angular.module('app1', []);
app.controller('app1_Ctrl', ["$scope", function($scope) {
.
.
.
}]);
app.directive('app1_Dir1', [function() {
function link(scope, element, attr) {
scope.$watch(attr.someAttrOfCheckBox, function() {
// some logic here
});
function submit() {
// some logic here
}
}
return link;
}]);
})();
How does $scope.value passed in scope in directive so that I can do some manipulations there? Will ng-click fire the function submit() in the directive link? Is it correct to use scope.$watch to listen for an action (ticked or unticked of course) in checkbox element?
Many thanks to those who can explain.
By default, directive scope is controller $scope; but it means the directive is directly dependent on your controller and you need a different controller for each instance of the directive you want to use. It is usually considered a best practice to isolate your directive scope and specifically define the variables you wish to pass it from your controller.
For this, you will need to add a scope statement to your directive :
scope {
label :'#',
context : '=',
function : '&'
}
and update your view :
<my-directive label="labelFromController" context="ctxtFromController" function="myFunction()" ></my-directive>
The symbols denote the kind of thing you wish to pass through : # is for one-way binding (as a string in your directive), = is for two-way binding of an object (which enables the directive to update something in your controller), and & is for passing a function.
There are a lot of additional options and subtleties that are best explained by the Angular doc https://docs.angularjs.org/guide/directive. There are also some nice tutorials out there (e.g. http://www.sitepoint.com/practical-guide-angularjs-directives/)
Your submit() function is not attached to anything, so you won't be able to call if from your viewer. You need to define it as scope.submit = function() ... in your link function if you wish to access it.
You can use $watch for this kind of thing, but there are usually other more elegant ways to achieve this by leveraging the fact that angular already "watches" the variables it is aware of and monitors any changes he can (this can be an issue when some external service changes data for exemple, because angular cannot listen to events it is not made aware of). Here, you can probably simply associate the ng-model directive to your input checkbox to store its true/fale (checked/unchecked) value, and the ng-change or ng-click directives to act on it. The optimal solution will mostly depend on the exact nature of your business logic.
Some additional thoughts :
The HTML insides of your directive should be packaged in an inline template field, or in a separate HTML file referenced by the templateUrl field in your directive.
In your HTML code above, your directive is not referenced anywhere. It should be an element, attribute or class (and your directive definition should reflect the way it can be called, with the restrict field). Maybe you have omitted the line containing the directive HTML, but as it stands, your directive doesn't do anything.
To my knowledge, you don't need to return link. Think of it as the "body" of your directive, where you define the variables and functions you will call in the HTML.
Your directive doesn't actually need HTML code and the above thoughts might be irrelevant if you are going in a different direction, but encapsulating some kind of view behaviour that you want to reuse is probably the most common use of directives.
I am using the editable-text directive from the xeditable module for AngularJS. Is there a way to disable the directive for the entire page?
I thought about using replacing editable-text with {{variable}}, where variable="editable-text" to enable and variable="somethingElse" to disable. However, this produces meaningless attributes in the html.
It is possible to remove directive with another directive. For this, new directive should have higher priority than the one being removed, then in compilation state you search for elements with required directive and remove tag/class/element wholetogether. Here's a very simple realisation of this:
.directive("disableDirective", function () {
function compile (el, attr) {
var hasDirective = el[0].querySelectorAll("["+attr.disableDirective+"]");
[].forEach.call(hasDirective, function (el) {
el.removeAttribute(attr.disableDirective);
});
}
return {
priority: 100000,
compile: compile
};
})
In the following HTML DIV will be visible, thanks to our directive:
<body disable-directive="ng-hide">
<div ng-hide="true">Hidden</div>
</body>
You'll have to set disable-directive="editable-text" for the page.
JSBin.
If you just want to turn off this directive across all app forever, I know two ways:
Create decorator for this directive and just set the compile function to empty function e.g. to angular.noop. Check this stuff for more info: https://docs.angularjs.org/api/auto/service/$provide#decorator .
Or you can also create directive with the same name and restrictions (A|E|C|M), but with higher priority and terminal property setted to true. But you should always keep in mind that this directive will turn off all directives with lower priority on the same element. (so this solution looks like antipattern for me)
I believe you can remove the restrict field using a decorator. this would disable the directive. Here is an example using ng-show
var app = angular.module("app", []);
//disable ng-show
app.config(function($provide) {
$provide.decorator('ngShowDirective', function($delegate) {
var directive = $delegate[0];
directive.restrict = "";
return $delegate;
});
});
I have a custom directive that I'm using in my templates. It does a bit of DOM work for me. I would like the host view/controller that I'm using the directive in to be able to run methods on my directive (and it's controller). But I'm not sure how best to call into the directives scope.
Example Fiddle
My view code:
<div ng-app="app">
<div ng-controller="MainCtrl">
<h3>Test App</h3>
<button ng-click="scopeClear()">Parent Clear</button>
<div my-directive string="myString"></div>
</div>
</div>
Here is the custom directive:
angular.module('components', []).directive('myDirective', function() {
function link(scope, element, attrs) {
scope.string = "";
scope.$watch(attrs.string, function(value) {
scope.string = value;
});
}
return {
controller: function($scope, $element) {
$scope.reset = function() {
$scope.string = "Hello";
}
$scope.clear = function() {
$scope.string = "";
}
},
template:
"<button ng-click='reset()'>Directive Reset</button>" +
"<button ng-click='clear()'>Directive Clear</button><br/>" +
"<input type='text' ng-model='string'>",
link: link
}
});
And controller:
angular.module('app', ['components']).controller('MainCtrl', function($scope) {
$scope.myString = "Hello";
$scope.scopeClear = function() {
// How do I get this to call the clear() method on myDirective
}
});
The workaround I found is jQuery('#my_directive').scope().myMethod(); But this seems wrong, like I'm missing some better part of angular to do this.
It also seems like and $emit isn't right here since I want a targeted method so it won't trigger on additional instances of the directive I have on the same page.
How would I access the directives methods from my parent controller?
I'm not sure I fully understand your objective here, and it's possible you could find a better pattern completely. Typically, directives display the state of the scope which is either an isolate scope (if they are self-sufficient) or a shared scope. Since you are not creating an isolate scope then they inherit the scope from the controller. If they are displaying data inherited from the controller then you don't want your controller calling into the directive, rather the directive will simply "redraw" itself whenever the properties in the controller change.
If you, instead, are looking to recalculate some stuff in your directives based on events from outside the directive you don't want any tight coupling - especially if building an entirely separate module. In that case, you might simply want to use $broadcast from the $scope within MainCtrl to broadcast an event that you may care about, and then your directive can provide the $on('eventName') handler. This way it's portable to any controller/scope that will fire such an event.
If you find yourself needing to know the exact properties in the controller or the exact functions within the directive then I would suggest that you have too-tightly coupled these pieces and they don't belong in separate modules since they could never be reused. Angular directives and controllers are not objects with functions, but objects that create scope and update frequently via $digest calls whenever properties in that scope change. So you may be able to find a way to better model the data, objects, and properties you are displaying. But I can't say without greater context.