Is possible (I'm guessing it is) to dynamically change template of a directive, depending on certain factors?
So, I see how theoretically it's possible - you can put all different templates into the $templateCache beforehand, and retrieve the one you need during directive compilation
something like:
restrict:'E'
scope:
data:'='
template:'='
link:(scope, elem, attrs)->
html = $templateCache.get(scope.template)
tElement.html(html)
However real question is, would be the right thing to do so? Will that badly affects performance? If for example directive used as a cell in a big grid?
Switching between Directives is a bad practice.
The Why:
Lets assume you have a DropDown Menu ( <select> ), and you Have set couple of directives that each one is linked to its option (by ngModel).
When the App runs,
You will start with initial directive set by the default value of the <select> property, and when you would try to replace that value, you would have to "Delete" the directive from the DOM and "Write" use a new one.
By now this should sound to you that it sound like jQuery.
The What Else should I do:
Well, you almost answered it for your self: use ng-switch.
ng-switch by AngularJS
Notes:
1) Switching between directive templates is possible (in the exact way you mentioned it, you set couple of templates on the background, and you load the one that fits the most according to the user interaction), but it sounds like you are about to type serious Spaghetti Code Which will be very difficult to maintain.
2) Performance: I can't tell if that would affect seriously on the performance of the app, but it will definitely be much harder to maintain.
3) If ng-switch doesn't fit there, You should look at this problem in a different angle.
EDIT:
Yes, It's common to do so:
And I've done that in couple of projects.
You have to pass a function to your template property of the new .directive.
Example:
templateUrl: function (elem, attrs){
return attrs["template"] == "table" ? "tableTemplate.html" : "listTemplate.html";
}
What this means : is that you have to add a so called "Support" attribute to your new directive. In this case I called it template.
It would look like so : <div new-directive="data" template="table"></div>
Related
I do not quite understand when to use a directive and when it would be more appropriate to use nginclude. Take this example: I have a partial, password-and-confirm-input-fields.html, that is the html for entering and confirming a password. I use this both under signup-page and under change-password-page. Those two pages has a controller each, the partial html has no dedicated controller.
Should I use directive or ngInclude for this?
It all depends on what you want from your code fragment. Personally, if the code doesn't have any logic, or doesn't even need a controller, then I go with ngInclude. I typically put large more "static" html fragments that I don't want cluttering up the view here. (ie: Let's say a large table whose data comes from the parent Controller anyway. It's cleaner to have <div ng-include="bigtable.html" /> than all those lines cluttering up the View)
If there is logic, DOM manipulation, or you need it to be customizable (aka render differently) in different instances it's used, then directives are the better choice (they're daunting at first, but they're very powerful, give it time).
ngInclude
Sometimes you will see ngInclude's that are affected by their exterior $scope / interface. Such as a large/complicated repeater lets say. These 2 interfaces are tied together because of this. If something in the main $scope changes, you must alter / change your logic within your included partial.
Directives
On the other hand, directives can have explicit scopes / controllers / etc. So if you're thinking of a scenario where you'd have to reuse something multiple times, you can see how having its own scope connected would make life easier & less confusing.
Also, anytime you are going to be interacting with the DOM at all, you should use a directive. This makes it better for testing, and decouples these actions away from a controller / service / etc, which is something you want!
Tip: Make sure not to use restrict: 'E' if you care about IE8! There are ways around this, but they are annoying. Just make life easier and stick with attribute/etc. <div my-directive />
Components [Update 3/1/2016]
Added in Angular 1.5, it's essentially a wrapper around .directve(). Component should be used most of the time. It removes a lot of boilerplate directive code, by defaulting to things like restrict: 'E', scope : {}, bindToController: true. I highly recommend using these for almost everything in your app, in order to be able to transition to Angular2 more easily.
In conclusion:
You should be creating Components & Directives a majority of the time.
More extensible
You can template and have your file externally (like ngInclude)
You can choose to use the parent scope, or it's own isolate scope within the directive.
Better re-use throughout your application
Update 3/1/2016
Now that Angular 2 is slowly wrapping up, and we know the general format (of course there will still be some changes here and there) just wanted to add how important it is to do components (sometimes directives if you need them to be restrict: 'E' for example).
Components are very similar to Angular 2's #Component. In this way we are encapsulating logic & html in the same area.
Make sure you encapsulate as many things as you can in components, it will make the transition to Angular 2 that much easier! (If you choose to make the transition)
Here's a nice article describing this migration process using directives (very similar if you were going to use components of course) : http://angular-tips.com/blog/2015/09/migrating-directives-to-angular-2/
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.
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.
I'm very(!) new to Angular.js and am trying to understand where to put the various parts of my logic in order to follow best practices and separate business and presentation logic.
My specific use case is that I have a list of courses with a number of signups and a number of available seats. Based on these values I want to present a progress bar (or, if the available seats is not set, just a text).
My question is where to put the various parts of the logic, and how to pass the values along properly. So far I've created the HTML-part of a directive, like so:
<signupprogress available="{{course.available_seats}}" filled="{{course.filled_seats}}"></signupprogress>
My question is then (first and foremost) if a directive is the proper way to do this and, of so, if the logic for constructing the progress bar should go in the compile function, link function, in a template, or some other place. To me the compile of link function seems to be most correct, but I don't want to fill them with HTML, nor am I able to properly get the attribute values from the HTML (the attrs.$observe examples I've seen only implement the getting of one attribute).
Yes, use a directive since you need to modify the DOM.
If all of the HTML for the progress bar and the alternative text can be placed in the directive's template, then do that. And, if that is possible, use '#' for one-way binding, which makes it clear that the directive does not need to modify the "available" and "filled" values. If you find you need a linking function, then as #ShaiRez mentioned, '=' is probably easier. (BTW, here is a way to $watch multiple attributes, instead of using $observe. Maybe the same technique can be used for $observe.)
To display either a progress bar or the alternative text in the template, use ng-hide or ng-show in the template. Here's a simple example of that (which also uses '#').
The directive is the way to go in my opinion.
I would have my HTML content inside of a template, the logic inside of the link function (the compile function is usually more for repeaters etc).
And use the "scope" property in the directive definition, set to "=", that way changes are reflected automatically.
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.