Egghead.io has a great explanation of directive restrictions in AngularJS. A custom directive can be defined as follows:
angular.module("app", []).directive("blah", function () {
return {
restrict: "A",
template: "blah directive. nothing to see here."
};
});
This creates a what I will call an attribute directive (due to restrict: "A") for the purposes of asking this question. This is actually Angular's default restriction on a custom directive, and this directive can be used like:
<div blah>
<!-- content of directive -->
</div>
When I want to create a custom directive, however, I normally go for an element directive, like:
<blah>
<!-- content of directive -->
</blah>
How is the former attribute directive any better than the latter element directive and why was it chosen as the default?
This is my opinion, only:
There are three possible ways to define a directive in the HTML - attributes, elements and classes. Classes are too lax and confusing, and a best practice would be to separate logic from style. Element directives are too strict - you can only have one "element" directive in a DOM element. That makes them special right from the start.
An attribute seems to be the best middle ground between these two extremes - it is clear, allows for multiple directives in an element (If you are following Egghead's videos, the superhero example on "directive to directive communication" shows a kind of "directive hierarchy", elements superseding attributes. Also, and this is very important most of the times (I program for intranet apps, so to me it isn't) attributes allow angularJS templates to be valid HTML.
EDIT - my two cents would be that it doesn't matter - in any real scenario, it is a bad idea to trust the "default" configuration of something as primary as the restrict option - setting it explicitly makes for clear, no doubt directives (especially working in team projects, but also anytime, really)
Angular 1.3 changed the default to be 'AE'. See: https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object.
I'm guessing it was changed because it's "restrict", not "include". So, the default for "restrict" should restrict very little.
What does that mean going forward -- if you see a "restrict: 'E'" line, it actually means "This wasn't meant to be used as an attribute", rather than the potential current meaning of "I simply want to use this as an element".
My take on this would be:
An element-based directive would most often represent structural functionality.
For instance, I use element-based directives for popups, dialogs, tabbed widgets, and reusable widgets in general. I then can add attribute-based directives to them (say, adding an ng-click to a <ui-button> directive), but the element name (i.e. the directive name) represents the structural semantics of what is being built.
Related
I totally hate to add tag names based on component names in my apps. I hate to do this:
<hero-detail hero="ctrl.hero"></hero-detail>
Isn’t it possible to use normal div tags instead? Something like this?
<div id="hero-detail" data-hero="ctrl.hero"></div>
why isn’t that working?
You can technically restrict a component to an attribute as you do with directives since components are a special kind of directive. (I modified a random fiddle found on google)
EDIT: this does work on older 1.5 versions but not on newer ones. So read on...
But officially you cannot and you shouldn't do it anyway. If you choose a component architecture, you have to stick to the rules or things will get out of hands quickly.
Having components as html elements is clean and good practice. You should learn to like it.
why not, that is also possible.
basically in three ways we can specify the directives.
i.e Element, Attribute, Classname
but while create the directive you can specify
restriction : AE
or whaterver you need,
then you can use like this
<div id="hero-detail" hero-detail data-hero="ctrl.hero"></div>
for more have a deep look at the docs. directives
your answer is here https://docs.angularjs.org/guide/directive
They says:
Directive types $compile can match directives based on element names,
attributes, class names, as well as comments.
All of the Angular-provided directives match attribute name, tag name,
comments, or class name. The following demonstrates the various ways a
directive (myDir in this case) can be referenced from within a
template:
<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive: my-dir exp -->
<span class="my-dir: exp;"></span>
Best Practice: Prefer using directives via tag name and attributes
over comment and class names. Doing so generally makes it easier to
determine what directives a given element matches.
Best Practice: Comment directives were commonly used in places where
the DOM API limits the ability to create directives that spanned
multiple elements (e.g. inside elements). AngularJS 1.2
introduces ng-repeat-start and ng-repeat-end as a better solution to
this problem. Developers are encouraged to use this over custom
comment directives when possible.
We know that there are four types of directives:
HTML element (E)
an attribute on an element (A)
a CSS class (C)
comment (M)
We can use restrict: 'EACM' in a directive. But can someone give a practical example when we need a directive to be element, attribute, css class and comment? Thanks.
Basically and really quick :
The only real used values are E and A.
E is element directive. This is the logical type of restriction to use for directives injecting content (template or templateUrl). This would be the nearest that AngularJS has to offer to webcomponents.
A is attribute. This is the logical type of restriction to enhance an element by adding behaviour to an element inside a template or to an E-directive element (example : ng-click, ng-show...) .
C is more or less useless. Same expected usages as A directives but less visible when you read your Angular HTML code because you're gonna mix styling css classes and directive css classes. Subject to risk when your designer removes directive classes from your template, for not finding them in your css files (true story). Quicker answer would be: don't use it, use A instead.
Best off all, M is plainly totally useless in 99.999% of usages. This is basically here for markup validation (avoiding invalid tags or attributes that can be caused by the E and A directives) for psychotics who hope to (and think it's a good/required thing to) have Angular templates validating any w3 validator service. Can also be used for backward compatibility in rare cases. Quicker answer for M would be: it's an hack, don't use it.
A simple example to explain my case:
I have a directive for labels
<input label="{{obj.label}}"/>
But for some other directives I want to use an attribute with name "label"
<other-directive label="My label"></other-directive>
just as an attribute, not processing the label-directive.
I could just rename the attribute to e.g. "my-label":
<other-directive my-label="My label"></other-directive>
but it would be nice to use "label" as an attribute name.
As #ExpertSystem points out in the comments to the question, angular really has no way of knowing out of the box whether your directive should be applied in one case versus another. The only way I can think of to get around this is to include logic in your directive's compile function that knows how to determine whether it should be applied or not. This plunker demonstrates how I would accomplish this. You basically need to return two different link functions from the compilation phase; one if your directive should be applied (in this case adding a label before the input), and a different one if it should be skipped. You are then free to use that as an argument to a separate directive. This may not work if your directive needs to do things like transclusion or isolated scopes (things that angular doesn't like two directives on the same element to do).
I'd be very sparing with how you use this, however, as it will create an inconsistent API for other developers who may be working with this code. They may not know when the directive will apply and when it won't.
I've been researching this for a few hours now.
Let's say I have a jQuery selector of $('#bottom .downloads .links a').click.....
How can I do the same type of thing in an Angular directive?
This is what I have so far and it works, but for all tags on the page.
angular.module('directives', []).directive('a', function(mongoDB){ //Don't need ['customServices'], it can just be [] to use mongoDB
return {
restrict : 'E',
link : function(scope, element, attrs){
element.on('click', function(){
//But this gets called for all links on the page
//I just want it to be links within the #bottom .downloads .links div
//I wanted to use a directive instead of ng-click="someMethod()"
});
}
});
Is there a way to target this directive to only a certain div? I guess I could change the restrict to 'C' or 'A' and add an attribute to the links, but I was wondering if I could still layout the front end like I currently am used to with my jQuery selectors.
There is a pretty significant philosophical difference between AngularJS and jQuery. In jQuery, everything is in the DOM - including your data - and you do everything through DOM transformations. AngularJS, on the other hand, has separation of concerns built in: models, views, controllers, services, etc., are all separate. We use controllers to glue code together, but each component knows nothing about the other components.
So whereas in jQuery, one might use a selector to find all links matching a certain pattern and then add a certain functionality to it (say a click handler), in AngularJS, the HTML is the "offical record". Instead of abstracting away the attachment of a click handler into a JavaScript function, it is put right into the markup:
<a ng-click="doWhatever()">Click me!</a>
In this case, doWhatever is a method on the scope for that part of the page, probably set in your controller:
$scope.doWhatever = function () {
console.log("Hello!");
}
So the way you are approaching the problem is not going to work in AngularJS. Instead, you need to look at directives not like jQuery selectors with a function, but as an extension of HTML. You ask yourself, "what does HTML not do out of the box that I need it to?" Your answer is your directive.
But AngularJS already has a built-in directive for click handlers (the ngClick used above).
Angular already has an a directive, so you probably shouldn't create your own.
In an Angular world, to "target only a certain div" (well, <a> within the div) we declaratively target that <a> with a directive, rather than use CSS-like selectors. So yes, restrict to 'A' and add an attribute to the <a> would be best:
<a ... target-this-one>...</a>
I personally think this reads better. Looking at the HTML it is clear which <a>s have special/additional functionality.
As #Josh pointed out, you would only need to do this if ng-click isn't sufficient for your needs.
When using server-side templating and client-side angularjs, I'm not able to get angularjs to recognize values I've templated in on the server.
For example (or on jsfiddle):
<div ng-app>
<div ng-controller="Ctrl">
<textarea ng-model="data" placeholder="Enter a name here">Templated in</textarea>
{{data}}
</div>
</div>
Angularjs will always replace the value in the text area with the value of $scope.data (which is null). What I want is for the value of $scope.data to take on "Templated in", on app bootstrap, then to continue normally from there.
How can I template in from the server a value, then have angularjs model bind that value once on the client?
Use ng-init
<textarea ng-model="data" placeholder="Enter a name here"
ng-init="data='Templated in'"></textarea>
See also AngularJS - Value attribute on an input text box is ignored when there is a ng-model used? and
rails + angularjs loading values into textfields on edit
I came across this answer while trying to figure this out for myself, but never liked that I had to move information from the part of the field where the HTML spec wants it. That just didn't feel like the right approach to me.
For a while, I ended up taking advantage of the fact that Angular sits on top of jQuery (or the embedded jqLite subset if the full jQuery isn't available) to solve this without moving the content, or even touching ng-init. Specifically, you can access the contents of the textarea using (the Angular/jqLite version of) standard jQuery methods during the controller initialization phase.
So I just did
var doc = angular.element(document.documentElement);
$scope.data = doc.find('textarea').eq(0).val();
in my controller, exactly where I would initialize any other scope variable. (Alternately, here's the modified jsFiddle...)
With the full jQuery library available, the code is even simpler, since you have access to full jQuery selectors at that point and can jump straight to $('textarea').eq(0).val() (or even add an id to the field and select on that: $('#data-textarea').val()).
The nice thing about this approach is that it will work for any form elements, though since most of those are <input> tags, and jqLite doesn't support selectors, finding the exact one you want can be a tad tricky. I would simply include the full jQuery library at that point and take advantage of the selectors, but that's me.
This approach also has the major drawback of placing DOM-aware code in the controller, which completely breaks both Angular conventions (The Angular Way (TM)) and the MVC/MVVM programming paradigm. Not the best solution.
UPDATE: So I eventually realized I would need a more long-term solution, one which didn't violate so many best practices. The answer actually comes from the most essential element of Angular, without which none of the rest of it would work (you could argue that for other components, but it's simply more true for this one): Directives. More specifically:
app.directive('input', ['$parse', function ($parse) {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs) {
if(attrs.value) {
$parse(attrs.ngModel).assign(attrs.value, scope);
}
}
};
}]);
This has a number of advantages. First, it doesn't break Angular conventions that help reinforce MVC/MVVM. Second, it doesn't even touch jqLite/jQuery, nor the underlying DOM functions. Third, it has the desired effect of preserving HTML conventions for defining default values, allowing (or at least simplifying) the use of Angular with other existing technologies, such as server-side templating engines.
Why doesn't Angular do this by default? Well, I don't know the actual answer without a bit more research, but a likely answer is that HTML conventions favor static page content, and Angular is designed for dynamic page content. That means breaking out of HTML conventions in many places, not letting it limit what is possible with Angular apps. Since controllers are expected to carry the responsibility of initializing models (and in most cases such an expectation is the correct one), the Angular team would have incentive to ignore the contents of a value attribute (and its analogs among the other form tags).
Of course, with this approach, for any applicable elements that already exist before the controller is initialized, the controller init can override the value attribute. This behavior is not consistent, however, across the entire application, because new elements will trigger the directive evaluation, but not the controller init phase. (Partials with their own controllers hold this behavior as well - elements in place prior to the partial's controller init can be overridden by said init, but other elements added after that won't re-trigger the init.)
There are a number of other ways to write such a directive, and it can be extended to do any number of other things as well, but hopefully this approach helps someone else.