What is the difference between compile and link function in angularjs - javascript

Can someone explain in simple terms?
The docs seems a bit obtuse. I am not getting the essence and the big picture of when to use one over the other. An example contrasting the two would be awesome.

compile function - use for template DOM manipulation (i.e., manipulation of tElement = template element), hence manipulations that apply to all DOM clones of the template associated with the directive.
link function - use for registering DOM listeners (i.e., $watch expressions on the instance scope) as well as instance DOM manipulation (i.e., manipulation of iElement = individual instance element). It is executed after the template has been cloned. E.g., inside an <li ng-repeat...>, the link function is executed after the <li> template (tElement) has been cloned (into an iElement) for that particular <li> element. A $watch() allows a directive to be notified of instance scope property changes (an instance scope is associated with each instance), which allows the directive to render an updated instance value to the DOM -- by copying content from the instance scope into the DOM.
Note that DOM transformations can be done in the compile function and/or the link function.
Most directives only need a link function, since most directives only deal with a specific DOM element instance (and its instance scope).
One way to help determine which to use: consider that the compile function does not receive a scope argument. (I'm purposely ignoring the transclude linking function argument, which receives a transcluded scope -- this is rarely used.) So the compile function can't do anything you would want to do that requires an (instance) scope -- you can't $watch any model/instance scope properties, you can't manipulate the DOM using instance scope information, you can't call functions defined on the instance scope, etc.
However, the compile function (like the link function) does have access to the attributes. So if your DOM manipulations don't require the instance scope, you can use a compile function. Here's an example of a directive that only uses a compile function, for those reasons. It examines the attributes, but it doesn't need an instance scope to do its job.
Here's an example of a directive that also only uses a compile function. The directive only needs to transform the template DOM, so a compile function can be used.
Another way to help determine which to use: if you don't use the "element" parameter in the link function, then you probably don't need a link function.
Since most directives have a link function, I'm not going to provide any examples -- they should be very easy to find.
Note that if you need a compile function and a link function (or pre and post link functions), the compile function must return the link function(s) because the 'link' attribute is ignored if the 'compile' attribute is defined.
See also
Difference between the 'controller', 'link' and 'compile' functions when defining a directive
Dave Smith's excellent ng-conf 2104 talk on directives (the link goes to the section of the video that talks about compile and link)

I beat my head against the wall on this for a couple of days, and I feel that a bit more explanation is in order.
Basically, the docs mention that the separation is largely a performance enhancement. I would reiterate that the compile phase is mainly used when you need to modify the DOM BEFORE the sub-elements themselves are compiled.
For our purposes, I'm going to stress terminology, which is otherwise confusing:
The compiler SERVICE ($compile) is the angular mechanism that processes the DOM and runs the various bits of code in directives.
The compile FUNCTION is one bit of code within a directive, which is run at a particular time BY the compiler SERVICE ($compile).
Some notes about the compile FUNCTION:
You cannot modify the ROOT element (the one your directive affects), since it is already being compiled from the outer level of DOM (the compile SERVICE has already scanned for directives on that element).
If you want to add other directives to (nested) elements, you either:
Have to add them during the compile phase.
Have to inject the compile service into the linking phase and compile the elements manually. BUT, beware of compiling something twice!
It is also helpful to see how the nesting and explicit calls to $compile work, so I've created a playground for viewing that at http://jsbin.com/imUPAMoV/1/edit. Basically, it just logs the steps to console.log.
I'll state the results of what you'd see in that bin here. For a DOM of custom directives tp and sp nested as follows:
<tp>
<sp>
</sp>
</tp>
Angular compile SERVICE will call:
tp compile
sp compile
tp pre-link
sp pre-link
sp post-link
tp post-link
The jsbin code also has the tp post-link FUNCTION explicitly call the compile SERVICE on a third directive (up), which does all three steps at the end.
Now, I want to walk through a couple of scenarios to show how one might go about using the compile and link to do various things:
SCENARIO 1: Directive as a MACRO
You want to add a directive (say ng-show) dynamically to something in your template that you can derive from an attribute.
Say you have a templateUrl that points to:
<div><span><input type="text"></span><div>
and you want a custom directive:
<my-field model="state" name="address"></my-field>
that turns the DOM into this:
<div><span ng-show="state.visible.address"><input ng-model="state.fields.address" ...>
basically, you want to reduce boilerplate by having some consistent model structure that your directive can interpret. In other words: you want a macro.
This is a great use for the compile phase, since you can base all of the DOM manipulations on things you know just from the attributes. Simply use jQuery to add the attributes:
compile: function(tele, tattr) {
var span = jQuery(tele).find('span').first();
span.attr('ng-show', tattr.model + ".visible." + tattr.name);
...
return {
pre: function() { },
post: function() {}
};
}
The sequence of operations will be (you can see this via the jsbin mentioned earlier):
The compile SERVICE finds my-field
It calls the compile FUNCTION on the directive, which updates the DOM.
The compile SERVICE then walks into the resulting DOM, and COMPILES (recursively)
The compile SERVICE then calls pre-link top-down
The compile SERVICE then calls post-link BOTTOM UP, so my-field's link function is called AFTER interior nodes have been linked.
In the above example, no linking is needed, since all of the directive's work was done in compile FUNCTION.
At any point, the code in a directive can ask for the compiler SERVICE to run on additional elements.
This means that we can do exactly the same thing in a link function if you inject the compile service:
directive('d', function($compile) {
return {
// REMEMBER, link is called AFTER nested elements have been compiled and linked!
link: function(scope, iele, iattr) {
var span = jQuery(iele).find('span').first();
span.attr('ng-show', iattr.model + ".visible." + iattr.name);
// CAREFUL! If span had directives on it before
// you will cause them to be processed again:
$compile(span)(scope);
}
});
If you're sure that the elements you are passing to $compile SERVICE originally were directive-free (e.g. they came from a template you defined, or you just created them with angular.element()), then the end result is pretty much the same as before (though you may be repeating some work). However, if the element had other directives on it, you just caused those to be processed again, which can cause all sorts of erratic behavior (e.g. double-registration of events and watches).
Thus, the compile phase is a much better choice for macro-style work.
SCENARIO 2: DOM configuration via scope data
This one follows from the example above. Suppose you need access to the scope while manipulating the DOM. Well, in that case, the compile section is useless to you, since it happens before a scope is available.
So, let's say you want to pimp out an input with validations, but you want to export your validations from a server-side ORM class (DRY), and have them auto-apply and generate the proper client-side UI for those validations.
Your model might push:
scope.metadata = {
validations: {
address: [ {
pattern: '^[0-9]',
message: "Address must begin with a number"
},
{ maxlength: 100,
message: "Address too long"
} ]
}
};
scope.state = {
address: '123 Fern Dr'
};
and you might want a directive:
<form name="theForm">
<my-field model="state" metadata="metadata" name="address">
</form>
to auto-include the proper directives and divs to show the various validation errors:
<form name="theForm">
<div>
<input ng-model="state.address" type="text">
<div ng-show="theForm.address.$error.pattern">Address must begin with a number</input>
...
In this case you definitely need access to the scope (since that is where your validations are stored), and are going to have to compile the additions manually, again being careful not to double-compile things. (as a side note, you would need to set a name on the containing form tag (I'm assuming theForm here), and could access it in link with iElement.parent().controller('form').$name).
In this case there is no point in writing a compile function. Link is really what you want. The steps would be:
Define a template that is completely devoid of angular directives.
Define a link function that adds the various attributes
REMOVE any angular directives that you might allow on your top-level element (the my-field directive). They have already been processed and this is a way to keep them from being double-processed.
Finish by calling the compile SERVICE on your top-level element
Like so:
angular.module('app', []).
directive('my-field', function($compile) {
return {
link: function(scope, iele, iattr) {
// jquery additions via attr()
// remove ng attr from top-level iele (to avoid duplicate processing)
$compile(iele)(scope); // will pick up additions
}
};
});
You could, of course, compile the nested elements one-by-one to avoid having to worry about the duplicate processing of ng directives when you compile the top-level element again.
One final note on this scenario: I implied you'd be pushing the definition of the validations from a server, and in my example I've shown them as data already in the scope. I leave it as an exercise for the reader to figure out how one might deal with needing to pull that data from a REST API (hint: deferred compile).
SCENARIO 3: two-way data binding via link
Of course the most common use of link is to simply hook up the two-way data binding via watch/apply. Most directives fall into this category, so it is adequately covered elsewhere.

From the docs:
Compiler
Compiler is an angular service which traverses the DOM looking for attributes. The compilation process happens into two phases.
Compile: traverse the DOM and collect all of the directives. The result is a linking function.
Link: combine the directives with a scope and produce a live view. Any changes in the scope model are reflected in the view, and any user interactions with the view are reflected in the scope model. Making the scope model a single source of truth.
Some directives such ng-repeat clone DOM elements once for each item in collection. Having a compile and link phase improves performance since the cloned template only needs to be compiled once, and then linked once for each clone instance.
So at least in some cases, the two phases exist separately as an optimization.
From #UmurKontacı:
If you are going to make DOM transformations, it should be compile. If you want to add some features that are behavior changes, it should be in link.

This is from Misko 's talk on directives. http://youtu.be/WqmeI5fZcho?t=16m23s
Think of the compiler function as
the thing that works on a template and the thing that is allowed to
change the template itself by, for example, adding a class to it or
anything like that. But it's the linking function that actually does
the work of binding the two together because the linking function has
access to the scope and it's the linking function that executes once
for each instantiation of the particular template. So the only kind of
things you can placed inside of the compile functions are things that
are common across all of the instances.

Little late to the thread. But, for the benefit of future readers:
I came across the following video which explains Compile and Link in Angular JS in a very great fashion:
https://www.youtube.com/watch?v=bjFqSyddCeA
It would not be pleasing to copy/type in all of the content here. I took a couple of screenshots from the video, which explain every stage of Compile and Link phases:
The second screenshot is little bit confusing. But, if we follow the step numbering, it is quite straight forward.
First cycle: "Compile" gets performed on all of the directives first.
Second cycle: "Controller" and "Pre-Link" gets performed (just one after another)
Third cycle: "Post-Link" gets performed in reverse order (starting from innermost)
Following is the code, which demonstrates the above:
var app = angular.module('app', []);
app.controller('msg', ['$scope', function($scope){
}]);
app.directive('message', function($interpolate){
return{
compile: function(tElement, tAttributes){
console.log(tAttributes.text + " -In compile..");
return {
pre: function(scope, iElement, iAttributes, controller){
console.log(iAttributes.text + " -In pre..");
},
post: function(scope, iElement, iAttributes, controller){
console.log(iAttributes.text + " -In Post..");
}
}
},
controller: function($scope, $element, $attrs){
console.log($attrs.text + " -In controller..");
},
}
});
<body ng-app="app">
<div ng-controller="msg">
<div message text="first">
<div message text="..second">
<div message text="....third">
</div>
</div>
</div>
</div>
UPDATE:
Part 2 of the same video is available here: https://www.youtube.com/watch?v=1M3LZ1cu7rw The video explains more about how to modify DOM and handle events during Compile and Link process of Angular JS, in a simple example.

Two Phases: Compile and Link
Compile:
Traverse the DOM tree looking for directives (elements / attributes / classes / comments). Each compilation of a directive may modify its template, or modify its contents which has not been compiled yet. Once a directive is matched, it returns a linking function, which is used in a later phase to link elements together. At the end of the compile phase, we have a list of compiled directives and their corresponding linking functions.
Link:
When an element is linked, the DOM tree is broken at its branch point in the DOM tree, and the contents are replaced by the compiled (and linked) instance of the template. The original displaced content is either discarded, or in the case of transclusion, re-linked back into the template. With transclusion, the two pieces are linked back together (kind of like a chain, with the template piece being in the middle). When the link function is called, the template has already been bound to a scope, and added as a child of the element. The link function is your opportunity to manipulate the DOM further and setup change listeners.

This question is old by I would like to make short summary which may help:
Compile called once for all directive instance
Compile main purpose is to return/create the link (and possibly pre/post) function/object. You can also init stuff that are shared between instances of the directive.
In my opinion, "link" is a confusing name for this feature. I would prefer "pre-render".
link is called for each directive instance and its purpose is to prepare the rendering of the directive in the DOM.

Related

communicating between directives controller

I know if I have two directives that are nesting I can communicate throw controller, require and pass it as the fourth parameter of the link function.
<my-first-div>
<my-seconded-div></my-seconded-div>
</my-first-div>
and every thing will work fine.
but I could do the same thing when they weren't nesting.
<my-first-div></my-first-div>
<my-seconded-div></my-seconded-div>
why ?
and how do I make them communicate ?
It happens since both of the directives have watchers on the same variable reference. So the changed value is being 'noticed' in all the relevant directives.
You could mimic this "communication" by passing the same variable (By Reference) (varName:'=') for both directives and place watchers on that variable inside each of these directives.
Then, the DOM hierarchy won't matter
For example:
Directive 1:
app.directive('directive1', function () {
return {
restrict: 'E',
scope: {
myVar: '='
}
link: function (scope, element, attrs) {
// do something with main directive
console.log("directive1", scope.myVar);
$scope.$watch("scope.myVar", function (value) {
console.log("directive1", "value changed to:" + scope.myVar)
});
}
}
});
The same for the second directive..
For both directives pass the same variable
And the magic will happen
I assume by saying communicating, you mean sharing data, state and events between two directives. I will list basic ways that I have in mind here:
The reason why you can pass data/state between two nested directives is because in AngularJS a child directive (nested one in your example) inherits the scope of it parents. As the results, two sibling directives can share same data from its parent controller.
<div ng-controller="ParentCtrl">
<ng-sibling></ng-sibling>
<ng-another-sibling></ng-another-sibling>
</div>
In the above piece of code, ng-sibling and ng-another-sibling will inherit the same data that is defined in their parent ParentCtrl
AngularJS support emitting/broadcasting event/data using $broadcast, $emit and $on function, document can be found here: https://docs.angularjs.org/api/ng/type/$rootScope.Scope.
$emit can be used to sent event upward the tree's hierarchy, while $broadcast's downward, and the direction is essential.
So one of your directive can dispatch the event, while the other listen to it. It's pretty similar to the way jQuery trigger events.
// In link function or controller of one directive
$scope.$broadcast("EVENT_A",my_data);
// Listen to EVENT_A on another directive
$scope.$on("EVENT_A",function($event,data){
....
})
While two-way binding or firing event arbitrarily can be useful at first, they can also lead to the situation when it's really difficult to keep track of the application's event and data flow. If you find this situation, maybe consider using Flux architecture with AngularJS not a bad idea. Redux fully supports AngularJS and all of your directives can be built as single-state components. The Github repo can be found here: https://github.com/angular-redux/ng-redux, and a simple tutorial on how to run AngularJS with Redux can be found here: http://blog.grossman.io/angular-1-using-redux-architecture/

How do I change one Directive when I click another Directive?

So here's my problem:
I have a page which displays two different graphs. Each of these graphs are there own Directives which has their own isolate scope.
When a user clicks on one of the bar's in the chart in Directive #1, I need the graph in Directive #2 to change.
Currently both Chart Directives are being fed their respective data sets from the Controller of this page.
Now from what I've seen I really have about three options:
Pass a callback function into Directive #1 which be called when the chart is selected. This callback function will exist on the Controller of the page and then can change the necessary data in order to get Directive #2 to update via data-binding.
Events. Fire an event on $rootScope inside of Directive #1 when the chart is selected. I can then listen to this event on the Controller and change the data in Directive #2 to update it via data-binding.
Use a Library like Rx.JS in order to make an observable inside of Directive #1. I haven't used Rx.JS with Angular that much so to be honest I have no idea if this would even work or what it would look like. But if I could expose this Observable to page's Controller from within Directive #1 then I should be able to subscribe to it and update Directive #2 when necessary.
Now I have a good understanding of Solution #1 and #2 but they have their own issues:
This very quickly could turn into "callback hell" and doesn't seem to be a very "Angular" solution. This also creates a bit of a tight dependency between the page's Controller and this very generic Chart Directive. Out of my options I think this is the best solution but I would love a better one.
I have to build a way to specify id's on the event names that are unique to that explicit instantiation of the directive, since theoretically there could be more than one of these Chart Directives on the page.
I would love to know if anyone has any other ideas that I haven't thought of or a better approach? Maybe even something that I'm not aware of that Rx.JS offers with Observable's?
TLDR: I need to click on Directive #1 and have it effect what is currently being displayed in Directive #2.
I think this can be done by using two binding scopes in your directive like,
.directive('graphOne', function () {
return {
template: blah/blah.html,
scope: {
scopeToPass: '='
}
}
})
and
.directive('graphTwo', function () {
return {
template: blah1/blah1.html,
scope: {
scopeToGet: '='
}
}
})
and in html
<graph-one scope-to-pass="uniqueScope"></graph-one>
<graph-two scope-to-get="uniqueScope"></graph-two>
Since we are assign $scope.uniqueScope to both directives, and the scopeToPass is two way binding, when the value of scopeToPass get changed it will be passed to uniqueScope and from uniqueScope it will be passed to scopeToGet.

Add AngularJS directive at runtime

I am creating a game where the first thing that needs to happen is some state is loaded in from an external JSON file - the contents of one of my directives are dependent on this data being available - because of this, I would like to delay applying the directive until after the data has loaded. I have written the following:
window.addEventListener('mythdataLoaded', function (e) {
// Don't try to create characters until mythdata has loaded
quest.directive('character', function() {
return {
restrict: 'A',
scope: {
character: '#'
},
controller: 'CharacterCtrl',
templateUrl: 'partials/character.html',
replace: true,
link: function(scope, element) {
$(document).on('click', '#'+scope.character, function () {
$('#'+scope.character+'-popup').fadeToggle();
});
}
};
});
});
// Load in myth data
var myth_data;
$.getJSON("js/mythdata_playtest.json", function(json) {
myth_data = json;
window.dispatchEvent(new Event('mythdataLoaded'));
});
However, it appears that my directive's link function never runs - I'm thinking this is because angular has already executed the part of it's cycle where directives are compiled/linked by the time this directive gets added. Is there some way to force angular to compile this directive after it is created? I googled around a bit, and some people suggested adding $compile to the link function for similar issues - but the link function is never run, so that doesn't work for this case. Thanks!
It seems to me it would be better to always configure the directive, to do the JSON call in the directive, and attach logic to the element in the JSON call's success handler. This would, if I understand you correctly, do what you want.
AngularJS is meant as a framework, not a library, so using it in the way you mentioned is not recommended. Exactly as you mentioned, AngularJS does a lot of things for you when it runs. AngularJS, by default, runs on document loaded, and your $.getJSON callback arrives after that. When AngularJS runs it does all its magic with compiling the content and all that.
As a sidenote, it's also more the Angular way to use $http over $.getJSON.
I think you're thinking about this the wrong way. A major ideology in angular is that you set up declarative elements and let it react to the state of the scope.
What I think you might want to do is pass in what you need through the directive scope, and use other angular built in directives to hide or show your default ("non directive") state until the scope gets set from the controller for example.
Example:
You want a box to be hidden until an api call comes back. Your directive sets special styles on your element (not hidden). Instead of delaying to dynamically set your directive, you can pass in a scope var with a default value and use something like ng-show="data.ready" in your directive template to handle the actual dom stuff.

Angular JS: What is the need of the directive’s link function when we already had directive’s controller with scope?

I need to perform some operations on scope and the template. It seems that I can do that in either the link function or the controller function (since both have access to the scope).
When is it the case when I have to use link function and not the controller?
angular.module('myApp').directive('abc', function($timeout) {
return {
restrict: 'EA',
replace: true,
transclude: true,
scope: true,
link: function(scope, elem, attr) { /* link function */ },
controller: function($scope, $element) { /* controller function */ }
};
}
Also, I understand that link is the non-angular world. So, I can use $watch, $digest and $apply.
What is the significance of the link function, when we already had controller?
After my initial struggle with the link and controller functions and reading quite a lot about them, I think now I have the answer.
First lets understand,
How do angular directives work in a nutshell:
We begin with a template (as a string or loaded to a string)
var templateString = '<div my-directive>{{5 + 10}}</div>';
Now, this templateString is wrapped as an angular element
var el = angular.element(templateString);
With el, now we compile it with $compile to get back the link function.
var l = $compile(el)
Here is what happens,
$compile walks through the whole template and collects all the directives that it recognizes.
All the directives that are discovered are compiled recursively and their link functions are collected.
Then, all the link functions are wrapped in a new link function and returned as l.
Finally, we provide scope function to this l (link) function which further executes the wrapped link functions with this scope and their corresponding elements.
l(scope)
This adds the template as a new node to the DOM and invokes controller which adds its watches to the scope which is shared with the template in DOM.
Comparing compile vs link vs controller :
Every directive is compiled only once and link function is retained for re-use. Therefore, if there's something applicable to all instances of a directive should be performed inside directive's compile function.
Now, after compilation we have link function which is executed while attaching the template to the DOM. So, therefore we perform everything that is specific to every instance of the directive. For eg: attaching events, mutating the template based on scope, etc.
Finally, the controller is meant to be available to be live and reactive while the directive works on the DOM (after getting attached). Therefore:
(1) After setting up the view[V] (i.e. template) with link. $scope is our [M] and $controller is our [C] in M V C
(2) Take advantage the 2-way binding with $scope by setting up watches.
(3) $scope watches are expected to be added in the controller since this is what is watching the template during run-time.
(4) Finally, controller is also used to be able to communicate among related directives. (Like myTabs example in https://docs.angularjs.org/guide/directive)
(5) It's true that we could've done all this in the link function as well but its about separation of concerns.
Therefore, finally we have the following which fits all the pieces perfectly :
Why controllers are needed
The difference between link and controller comes into play when you want to nest directives in your DOM and expose API functions from the parent directive to the nested ones.
From the docs:
Best Practice: use controller when you want to expose an API to other directives. Otherwise use link.
Say you want to have two directives my-form and my-text-input and you want my-text-input directive to appear only inside my-form and nowhere else.
In that case, you will say while defining the directive my-text-input that it requires a controller from the parent DOM element using the require argument, like this: require: '^myForm'. Now the controller from the parent element will be injected into the link function as the fourth argument, following $scope, element, attributes. You can call functions on that controller and communicate with the parent directive.
Moreover, if such a controller is not found, an error will be raised.
Why use link at all
There is no real need to use the link function if one is defining the controller since the $scope is available on the controller. Moreover, while defining both link and controller, one does need to be careful about the order of invocation of the two (controller is executed before).
However, in keeping with the Angular way, most DOM manipulation and 2-way binding using $watchers is usually done in the link function while the API for children and $scope manipulation is done in the controller. This is not a hard and fast rule, but doing so will make the code more modular and help in separation of concerns (controller will maintain the directive state and link function will maintain the DOM + outside bindings).
The controller function/object represents an abstraction model-view-controller (MVC). While there is nothing new to write about MVC, it is still the most significant advanatage of angular: split the concerns into smaller pieces. And that's it, nothing more, so if you need to react on Model changes coming from View the Controller is the right person to do that job.
The story about link function is different, it is coming from different perspective then MVC. And is really essential, once we want to cross the boundaries of a controller/model/view (template).
Let' start with the parameters which are passed into the link function:
function link(scope, element, attrs) {
scope is an Angular scope object.
element is the jqLite-wrapped element that this directive matches.
attrs is an object with the normalized attribute names and their corresponding values.
To put the link into the context, we should mention that all directives are going through this initialization process steps: Compile, Link. An Extract from Brad Green and Shyam Seshadri book Angular JS:
Compile phase (a sister of link, let's mention it here to get a clear picture):
In this phase, Angular walks the DOM to identify all the registered
directives in the template. For each directive, it then transforms the
DOM based on the directive’s rules (template, replace, transclude, and
so on), and calls the compile function if it exists. The result is a
compiled template function,
Link phase:
To make the view dynamic, Angular then runs a link function for each
directive. The link functions typically creates listeners on the DOM
or the model. These listeners keep the view and the model in sync at
all times.
A nice example how to use the link could be found here: Creating Custom Directives. See the example: Creating a Directive that Manipulates the DOM, which inserts a "date-time" into page, refreshed every second.
Just a very short snippet from that rich source above, showing the real manipulation with DOM. There is hooked function to $timeout service, and also it is cleared in its destructor call to avoid memory leaks
.directive('myCurrentTime', function($timeout, dateFilter) {
function link(scope, element, attrs) {
...
// the not MVC job must be done
function updateTime() {
element.text(dateFilter(new Date(), format)); // here we are manipulating the DOM
}
function scheduleUpdate() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
scheduleUpdate(); // schedule the next update
}, 1000);
}
element.on('$destroy', function() {
$timeout.cancel(timeoutId);
});
...

Access Attributes / $element context from function called by Angular directive

Within a directive definition, there's an API for accessing $element and $attrs, so you can refer back to the element from which the directive was called. How would I access $element and $attrs when calling a custom function using a standard directive like ng-class?
EDIT: I understand that this is not an idiomatic approach. This question is applicable to rapid prototypes, which is a great use for many of Angular's features. Just not the ones that are all about sustainability, separation of concerns, etc. My primary concern is velocity. Thus, being able to bang something out quickly with the built-in directives and a quick controller method can, in fact, be a virtue, and can win the opportunity to do a fuller and more proper implementation down the road...
In this case, I'm just adding a contextual .active class to a nav element, based on the value of $location.path(), as per this post and this one. However, in those examples you need to explicitly and redundantly pass a copy of the contents of the href attribute as an argument to the getClass() function you're calling from ng-class. But isn't there a way to programmatically access the href attribute from within getClass() and avoid redundantly passing identical content as an arg?
For example, a controller with a getClass() function the way I'd imagine it could work:
function navCtrl($scope, $routeParams) {
$scope.getClass = function($element, $attrs) {
if ($location.path().substr(0, path.length) == $attrs.href) {
return "active"
} else {
return ""
}
}
}
Which you could then call simply and elegantly with:
<a ng-class="getClass()" href="/tasks">Tasks</a>
rather than:
<a ng-class="getClass('/tasks')" href="/tasks">Tasks</a>
(I recognize another option is to create a custom directive that does this, but for now I'd just like to figure out if it's possible to access $attrs and/or $element from within a controller function that's been called by a directive. Thanks!)
You actually can do this... BUT YOU SHOULD NOT DO THIS. lol...
Here's how to do what you're asking...
In your markup, pass $event into your function.
<a ng-click="test($event)" href="whatever">Click Me</a>
Then in your controller, get the target element of the event:
$scope.test = function($event) {
alert($event.target.href);
};
Now here is why you probably shouldn't do this:
If you reference the DOM, or manipulate the DOM in your controller, you're endangering dependency injection and also the separation of concerns from within the Angular structure. Sure you could still inject $event as a dependency if you were testing your function, but depending on what you're doing inside of that function, you might still have ruined your DI, and you're trying to make a controller do a directive's work (which is to try to keep your controller from being tightly-coupled to your markup)
That said, if ALL your doing is just getting a value, it's probably fine, but you're still coupling your controller to your markup to some degree. If you're doing anything else with that DOM element, you're off the reservation.
I don't think you can do that. The angular philosophy is to avoid accessing the DOM directly from your controller. You have already identified your two options for doing this in Angular:
pass in the href as a param i.e.. getClass('/tasks')
or, write a custom directive
Alternatively, if the class is purely presentational and doesn't affect how your application runs then you could ignore angular and use a quick and dirty jQuery function to do the job for you.
The lack of direct interaction with the DOM can be a bit strange at first but it's a godsend in the long term as your codebase grows, gets more complicated and needs more tests.
Here's a completely different answer: you don't need a Angular to change styling based on href. You can use CSS selectors for that.
In your styles:
a[href="/something"] {
background-color: red;
}
That in combination with anything you might be doing with ng-class should be everything you need.

Categories

Resources