Angularjs - directive best practice function in link or controller block? [duplicate] - angularjs

Some places seem to use the controller function for directive logic and others use link. The tabs example on the angular homepage uses controller for one and link for another directive. What is the difference between the two?

I'm going to expand your question a bit and also include the compile function.
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. (If you also need a link function (or pre and post link functions), and you defined a compile function, the compile function must return the link function(s) because the 'link' attribute is ignored if the 'compile' attribute is defined.)
link function - normally use for registering listener callbacks (i.e., $watch expressions on the scope) as well as updating the DOM (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 scope property changes (a scope is associated with each instance), which allows the directive to render an updated instance value to the DOM.
controller function - must be used when another directive needs to interact with this directive. E.g., on the AngularJS home page, the pane directive needs to add itself to the scope maintained by the tabs directive, hence the tabs directive needs to define a controller method (think API) that the pane directive can access/call. For a more in-depth explanation of the tabs and pane directives, and why the tabs directive creates a function on its controller using this (rather than on $scope), please see 'this' vs $scope in AngularJS controllers.
In general, you can put methods, $watches, etc. into either the directive's controller or link function. The controller will run first, which sometimes matters (see this fiddle which logs when the ctrl and link functions run with two nested directives). As Josh mentioned in a comment, you may want to put scope-manipulation functions inside a controller just for consistency with the rest of the framework.

As complement to Mark's answer, the compile function does not have access to scope, but the link function does.
I really recommend this video; Writing Directives by Misko Hevery (the father of AngularJS), where he describes differences and some techniques. (Difference between compile function and link function at 14:41 mark in the video).

running code before Compilation : use controller
running code after Compilation : use Link
Angular convention : write business logic in controller and DOM manipulation in link.
Apart from this you can call one controller function from link function of another directive.For example you have 3 custom directives
<animal>
<panther>
<leopard></leopard>
</panther>
</animal>
and you want to access animal from inside of "leopard" directive.
http://egghead.io/lessons/angularjs-directive-communication will be helpful to know about inter-directive communication

compile function -
is called before the controller and link function.
In compile function, you have the original template DOM so you can make changes on original DOM before AngularJS creates an instance of it and before a scope is created
ng-repeat is perfect example - original syntax is template element, the repeated elements in HTML are instances
There can be multiple element instances and only one template element
Scope is not available yet
Compile function can return function and object
returning a (post-link) function - is equivalent to registering the linking function via the link property of the config object when the compile function is empty.
returning an object with function(s) registered via pre and post properties - allows you to control when a linking function should be called during the linking phase. See info about pre-linking and post-linking functions below.
syntax
function compile(tElement, tAttrs, transclude) { ... }
controller
called after the compile function
scope is available here
can be accessed by other directives (see require attribute)
pre - link
The link function is responsible for registering DOM listeners as well as updating the DOM. It is executed after the template has been cloned. This is where most of the directive logic will be put.
You can update the dom in the controller using angular.element but this is not recommended as the element is provided in the link function
Pre-link function is used to implement logic that runs when angular js has already compiled the child elements but before any of the child element's post link have been called
post-link
directive that only has link function, angular treats the function as a post link
post will be executed after compile, controller and pre-link funciton, so that's why this is considered the safest and default place to add your directive logic

Related

Why directive has scope?

As far as I know that basically scope is an instance of a controller.
Every time I declare a controller scope will be available for that controller.
But then why directive has scope in link function ?
I didn't declare any controller for the directive.
Then why link function has scope ? Any Idea ?
From the doc:
scope:
The scope to be used by the directive for registering watches.
You may also be interested to see the differences between $scope and scope.
All directives have a scope associated with them. They use this scope for accessing data/methods inside the template and link function. By default, unless explicitly set, directives don’t create their own scope. Therefore, directives use their parent scope ( usually a controller ) as their own.
However, AngularJS allows us to change the default scope of directives by passing a configuration object known as directive definition object. A directive definition object –– let’s call it as DDO –– is a simple JavaScript object used for configuring the directive’s behaviour,template..etc. Check out AngularJS docs about DDO.
So, we can use scope inside linking function to work with parent scope, child scope or isolated scope.
You may visit this for detailed information about scope inside directive.
You can share the data between controller and linking function.
Sometimes we would like to access that same data in both controller and link functions in Angular directive. Usually we add this data to the scope object. This has certain problems, for example exposing it to any child scope. Add the shared properties to the controller instance itself and access in the link function.
You may also be interested in what is need of link function?
A directive is Angular's way of defining 'components': you specify how its markup looks and how it behaves before, during and after being rendered. The link functions allow you to hook up events to the DOM element before or after it gets 'linked' (see also: pre-link and post-link methods). When these events are fired you might want to change some of the variables within the scope, and that's why you have access to it within the link functions.

AngularJS: Calling a directive function from a controller the right way

I have two scenarios:
A function defined in a controller is called from a directive (see plunk).
The directive includes a '&' scope restriction to relate the controller and directive functions. If you click on the text, the element click event is triggered in the directive and the controller function is called. $scope.$apply() is used to notify AngularJS of the click event and refresh the value of var on the screen.
A function defined in a directive is called from a controller (see plunk).
The directive doesn't have any scope restriction, meaning that $scope in the controller and scope in the directive are shared. I defined func1() in the directive that can be called from the controller (try clicking on the text), however it seems intrusive as the controller needs to know the name of the function.
Is there a way to define func1() in such a way that the function name is declared in the directive div, similar to scenario 1?
Both options are possible and acceptable depending on how you are building the component.
Option 1 - Controllers should register listeners/callbacks and set attributes on directives (when using isolated scope - which i recommend). Controllers should not call a directives function but instead should change an attribute that has been bound on the directive. The directive if setup correctly should be watching this attribute and update accordingly.
Directives should not know who their controllers are in my opinion, it promotes decoupled code. Instead, controllers should be setting callback functions that directives can invoke at the appropriate time (i.e. letting the controller know something was clicked, selected, deleted, swiped, etc...)
Option 2 - Is acceptable to me when you are building a web component that provides the structure and behavioral logic but does not define the actual individual components UI (not including a possible skeleton UI). For example a list directive. The individual list may not be defined in the directive but "plugged in" for each context or use, giving us a more modular and reusable component. It does require us to know some things about the directive. It also requires that we modify the transclusion function to use the directives scope on the transcluded content instead of the original controllers scope. For an example you can checkout a list component I made as an example to this point.
http://github.com/Spidy88/ng-web-components
A snippet of text from this html page. The sf-list directive is an isolated scope directive that defines a lists behavior. However we can still define what each individual list item looks like with modified transclusion. It relies on us to call selectItem though in order to trigger selection behavior on the list.
<sf-list items="ctrl.emails" listener="ctrl.adapter">
<div class="list-item email" ng-click="selectItem(item)" ng-repeat="item in items track by item.id">
<div class="from">{{ item.from }}:</div>
<div class="subject">{{ item.subject }}</div>
</div>
</sf-list>

The difference between a compile/link function and a controller function in AngularJS? [duplicate]

Some places seem to use the controller function for directive logic and others use link. The tabs example on the angular homepage uses controller for one and link for another directive. What is the difference between the two?
I'm going to expand your question a bit and also include the compile function.
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. (If you also need a link function (or pre and post link functions), and you defined a compile function, the compile function must return the link function(s) because the 'link' attribute is ignored if the 'compile' attribute is defined.)
link function - normally use for registering listener callbacks (i.e., $watch expressions on the scope) as well as updating the DOM (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 scope property changes (a scope is associated with each instance), which allows the directive to render an updated instance value to the DOM.
controller function - must be used when another directive needs to interact with this directive. E.g., on the AngularJS home page, the pane directive needs to add itself to the scope maintained by the tabs directive, hence the tabs directive needs to define a controller method (think API) that the pane directive can access/call. For a more in-depth explanation of the tabs and pane directives, and why the tabs directive creates a function on its controller using this (rather than on $scope), please see 'this' vs $scope in AngularJS controllers.
In general, you can put methods, $watches, etc. into either the directive's controller or link function. The controller will run first, which sometimes matters (see this fiddle which logs when the ctrl and link functions run with two nested directives). As Josh mentioned in a comment, you may want to put scope-manipulation functions inside a controller just for consistency with the rest of the framework.
As complement to Mark's answer, the compile function does not have access to scope, but the link function does.
I really recommend this video; Writing Directives by Misko Hevery (the father of AngularJS), where he describes differences and some techniques. (Difference between compile function and link function at 14:41 mark in the video).
running code before Compilation : use controller
running code after Compilation : use Link
Angular convention : write business logic in controller and DOM manipulation in link.
Apart from this you can call one controller function from link function of another directive.For example you have 3 custom directives
<animal>
<panther>
<leopard></leopard>
</panther>
</animal>
and you want to access animal from inside of "leopard" directive.
http://egghead.io/lessons/angularjs-directive-communication will be helpful to know about inter-directive communication
compile function -
is called before the controller and link function.
In compile function, you have the original template DOM so you can make changes on original DOM before AngularJS creates an instance of it and before a scope is created
ng-repeat is perfect example - original syntax is template element, the repeated elements in HTML are instances
There can be multiple element instances and only one template element
Scope is not available yet
Compile function can return function and object
returning a (post-link) function - is equivalent to registering the linking function via the link property of the config object when the compile function is empty.
returning an object with function(s) registered via pre and post properties - allows you to control when a linking function should be called during the linking phase. See info about pre-linking and post-linking functions below.
syntax
function compile(tElement, tAttrs, transclude) { ... }
controller
called after the compile function
scope is available here
can be accessed by other directives (see require attribute)
pre - link
The link function is responsible for registering DOM listeners as well as updating the DOM. It is executed after the template has been cloned. This is where most of the directive logic will be put.
You can update the dom in the controller using angular.element but this is not recommended as the element is provided in the link function
Pre-link function is used to implement logic that runs when angular js has already compiled the child elements but before any of the child element's post link have been called
post-link
directive that only has link function, angular treats the function as a post link
post will be executed after compile, controller and pre-link funciton, so that's why this is considered the safest and default place to add your directive logic

Calling order of link function in nested and repeated angularjs directives

I'm fairly new to Javascript programming and I have only touched upon AngularJS. In order to evaluate it I decided to write a simple note application. The model is really simple, a list of notes where each note has a label, a text and a list of tags. However I ran into problem passing data between isolated scopes of nested directives.
I have three directives, notes, note and tagger (defining new elements with the same names). Each of them using an isolated scope.
The notes directive uses ng-repeat to "render" each one of its notes with the note element.
The note directive uses the tagger element to "render" the list of tags.
The note directive defines scope: { getNote: "&", ... } in order to pass a note instance from the list of notes to the note controller/directive. The getNote(index) function is called in the link function of the note directive. This works fine!
The tagger directive defines scope: { getTags: "&", ... } in order to pass a list of tags for a given note to the tagger controller/directive. The getTags function is called in the link function of the tagger directive. This does not work!
As I understand it, the problem is that the link functions of the directives are called in an inconsistent order. Debugging the application shows that the link functions are called in the following order:
link function in the notes directive
(adding the getNote function to the notes scope)
link function in the tagger directive of the first note
(calling getTags in the parent note scope) function
link function in the first note directive
(adding the getTags to the scope)
(calling getNote in the parent notes scope)
link function in the tagger directive of the second note
(calling getTags in the parent note scope) function
link function in the second note directive
(adding the getTags to the scope)
(calling getNote in the parent notes scope)
This will not work since in #2 the scope of the first note has not yet an getTags function.
A simplistic example can be found in Plunker.
Hence, my question boils down to: What determines the order in which link functions are called in nested directives.
(I solved to the problem using $watch on getTags in the tagger directive...)
regards
Quoting Josh D. Miller who had kindly responded to a similar question I had :
" Just a couple of technical notes. Assume that you have this markup:
<div directive1>
<div directive2>
<!-- ... -->
</div>
</div>
Now AngularJS will create the directives by running directive functions in a certain order:
directive1: compile
directive2: compile
directive1: controller
directive1: pre-link
directive2: controller
directive2: pre-link
directive2: post-link
directive1: post-link
By default a straight "link" function is a post-link, so your outer directive1's link function will not run until after the inner directive2's link function has ran. That's why we say that it's only safe to do DOM manipulation in the post-link. "
On a single element the order of the linking functions is determined by the order of the Compile Functions which in turn is ordered according to the priority property of the directive definition object.
Source : http://docs.angularjs.org/guide/directive
On multiple elements with transclusion : the inner directives are evaluated before outer directives. Reason: Nature of transclusion.
On multiple elements with siblings : Executed in order top to bottom. Reason: Parsing logic of $compile.

When shall we use `preLink` of directive's compile function?

The compile function of angularjs' directive has two functions: preLink and postLink.
Pre-linking function
Executed before the child elements are linked. Not safe to do DOM transformation since the compiler linking function will fail to locate the correct elements for linking.
Post-linking function
Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function.
It tells what we should not do in preLink, I wonder what and when should I use preLink? For most of time I just used postLink. Is there any case that we must use it?
You need almost never use preLink. Viable cases for it are when you need to manipulate data in scope, but not the DOM, before link functions (also of other directives) are executed.
As jacob commented, you can always do that from a controller too, but sometimes it's more appropriate to have the code in the directive itself.
There is an excellent article about how directives work where linking order is explained well to: http://www.jvandemo.com/the-nitty-gritty-of-compile-and-link-functions-inside-angularjs-directives/
If you need a good example of why pre-linking is sometimes necessary, I recommend you look at the code of angular directives themselves. For example https://github.com/angular/angular.js/blob/master/src/ng/directive/ngModel.js
A preLink function is used when the directive wants to put something into a shared scope so that it's ready to be used by other directives in their postLink functions.
Angular's form directive, e.g., creates an object that contains entries for all inputs. A custom directive could safely access this object in a postLink function.
I've had to use preLink when creating custom directives which include other directives. In my case, my directive included a template which applied Angular UI Bootstrap's Typeahead directive to some of its elements and used its own scope variables to initialize Typeahead features.
For example:
...
template:
"<select ng-show='dropdown' class='form-control' ng-model='ngModel' ng-options='s for s in suggestions'></select>"
+ "<textarea ng-show='!dropdown' class='form-control' ng-model='ngModel' typeahead='s for s in suggestions |filter:$viewValue' typeahead-min-length='0' typeahead-editable='{{editable}}'></textarea>",
...
In that case, Angular links the child directives before the parent, so I needed to use preLink to setup the typeahead. When I initialized the $scope.dropdown and $scope.editable variables in the directives postLink function, I found they were not initialized when the typeahead directives were linked and I had to move their initialization into the preLink to make this directive work correctly.

Resources