Link vs compile vs controller - angularjs

When you create a directive, you can put code into the compiler, the link function or the controller.
In the docs, they explain that:
compile and link function are used in different phases of the angular
cycle
controllers are shared between directives
However, for me it is not clear, which kind of code should go where.
E.g.: Can I create functions in compile and have them attached to the scope in link or only attach functions to the scope in the controller?
How are controllers shared between directives, if each directive can have its own controller? Are the controllers really shared or is it just the scope properties?

Compile :
This is the phase where Angular actually compiles your directive. This compile function is called just once for each references to the given directive. For example, say you are using the ng-repeat directive. ng-repeat will have to look up the element it is attached to, extract the html fragment that it is attached to and create a template function.
If you have used HandleBars, underscore templates or equivalent, its like compiling their templates to extract out a template function. To this template function you pass data and the return value of that function is the html with the data in the right places.
The compilation phase is that step in Angular which returns the template function. This template function in angular is called the linking function.
Linking phase :
The linking phase is where you attach the data ( $scope ) to the linking function and it should return you the linked html. Since the directive also specifies where this html goes or what it changes, it is already good to go. This is the function where you want to make changes to the linked html, i.e the html that already has the data attached to it. In angular if you write code in the linking function its generally the post-link function (by default). It is kind of a callback that gets called after the linking function has linked the data with the template.
Controller :
The controller is a place where you put in some directive specific logic. This logic can go into the linking function as well, but then you would have to put that logic on the scope to make it "shareable". The problem with that is that you would then be corrupting the scope with your directives stuff which is not really something that is expected.
So what is the alternative if two Directives want to talk to each other / co-operate with each other? Ofcourse you could put all that logic into a service and then make both these directives depend on that service but that just brings in one more dependency. The alternative is to provide a Controller for this scope ( usually isolate scope ? ) and then this controller is injected into another directive when that directive "requires" the other one. See tabs and panes on the first page of angularjs.org for an example.

I wanted to add also what the O'Reily AngularJS book by the Google Team has to say:
Controller - Create a controller which publishes an API for communicating across directives. A good example is Directive to Directive Communication
Link - Programmatically modify resulting DOM element instances, add event listeners, and set up data binding.
Compile - Programmatically modify the DOM template for features across copies of a directive, as when used in ng-repeat. Your compile function can also return link functions to modify the resulting element instances.

A directive allows you to extend the HTML vocabulary in a declarative fashion for building web components. The ng-app attribute is a directive, so is ng-controller and all of the ng- prefixed attributes. Directives can be attributes, tags or even class names, comments.
How directives are born (compilation and instantiation)
Compile: We’ll use the compile function to both manipulate the DOM before it’s rendered and return a link function (that will handle the linking for us). This also is the place to put any methods that need to be shared around with all of the instances of this directive.
link: We’ll use the link function to register all listeners on a specific DOM element (that’s cloned from the template) and set up our bindings to the page.
If set in the compile() function they would only have been set once (which is often what you want). If set in the link() function they would be set every time the HTML element is bound to data in the
object.
<div ng-repeat="i in [0,1,2]">
<simple>
<div>Inner content</div>
</simple>
</div>
app.directive("simple", function(){
return {
restrict: "EA",
transclude:true,
template:"<div>{{label}}<div ng-transclude></div></div>",
compile: function(element, attributes){
return {
pre: function(scope, element, attributes, controller, transcludeFn){
},
post: function(scope, element, attributes, controller, transcludeFn){
}
}
},
controller: function($scope){
}
};
});
Compile function returns the pre and post link function. In the pre link function we have the instance template and also the scope from the controller, but yet the template is not bound to scope and still don't have transcluded content.
Post link function is where post link is the last function to execute. Now the transclusion is complete, the template is linked to a scope, and the view will update with data bound values after the next digest cycle. The link option is just a shortcut to setting up a post-link function.
controller: The directive controller can be passed to another directive linking/compiling phase. It can be injected into other directices as a mean to use in inter-directive communication.
You have to specify the name of the directive to be required – It should be bound to same element or its parent. The name can be prefixed with:
? – Will not raise any error if a mentioned directive does not exist.
^ – Will look for the directive on parent elements, if not available on the same element.
Use square bracket [‘directive1′, ‘directive2′, ‘directive3′] to require multiple directives controller.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope, $element) {
});
app.directive('parentDirective', function() {
return {
restrict: 'E',
template: '<child-directive></child-directive>',
controller: function($scope, $element){
this.variable = "Hi Vinothbabu"
}
}
});
app.directive('childDirective', function() {
return {
restrict: 'E',
template: '<h1>I am child</h1>',
replace: true,
require: '^parentDirective',
link: function($scope, $element, attr, parentDirectCtrl){
//you now have access to parentDirectCtrl.variable
}
}
});

Also, a good reason to use a controller vs. link function (since they both have access to the scope, element, and attrs) is because you can pass in any available service or dependency into a controller (and in any order), whereas you cannot do that with the link function. Notice the different signatures:
controller: function($scope, $exceptionHandler, $attr, $element, $parse, $myOtherService, someCrazyDependency) {...
vs.
link: function(scope, element, attrs) {... //no services allowed

this is a good sample for understand directive phases
http://codepen.io/anon/pen/oXMdBQ?editors=101
var app = angular.module('myapp', [])
app.directive('slngStylePrelink', function() {
return {
scope: {
drctvName: '#'
},
controller: function($scope) {
console.log('controller for ', $scope.drctvName);
},
compile: function(element, attr) {
console.log("compile for ", attr.name)
return {
post: function($scope, element, attr) {
console.log('post link for ', attr.name)
},
pre: function($scope, element, attr) {
$scope.element = element;
console.log('pre link for ', attr.name)
// from angular.js 1.4.1
function ngStyleWatchAction(newStyles, oldStyles) {
if (oldStyles && (newStyles !== oldStyles)) {
forEach(oldStyles, function(val, style) {
element.css(style, '');
});
}
if (newStyles) element.css(newStyles);
}
$scope.$watch(attr.slngStylePrelink, ngStyleWatchAction, true);
// Run immediately, because the watcher's first run is async
ngStyleWatchAction($scope.$eval(attr.slngStylePrelink));
}
};
}
};
});
html
<body ng-app="myapp">
<div slng-style-prelink="{height:'500px'}" drctv-name='parent' style="border:1px solid" name="parent">
<div slng-style-prelink="{height:'50%'}" drctv-name='child' style="border:1px solid red" name='child'>
</div>
</div>
</body>

compile: used when we need to modify directive template, like add new expression, append another directive inside this directive
controller: used when we need to share/reuse $scope data
link: it is a function which used when we need to attach event handler or to manipulate DOM.

Related

Dependencies between angular directives

I've created a small app that has 2 directives: One that adds a google map div and initialize it, and the second, which shows layers that contain markers. The directives are independent, but I want them to communicate: The layers directive is independent, yet it needs to use the google maps directive to add markers to the map. I used $broadcast through $rootScope to communicate between the directives.
The directives are defined as follows:
angular.module('googleMapModule', [])
.directive('myGoogleMap', function(){
template: '<div id="map" />',
controller: function($scope){
// some initializations
// Listen for event fired when markers are added
$scope.$on('addMarkersEvent', function(e, data){
// do something
}
}
}
angular.module('layersDirective', [])
.directive('myLayers', function() {
templateUrl: 'someLayersHtml.html',
controller: function($http, $scope, $rootScope){
// Get layers of markers, etc.
// On specific layer click, get markers and:
$rootScope.broadcast('addMarkersEvent', {
data: myMarkers
});
}
});
After this long prologue here are my questions:
How should the connection between the two directives be implemented? Is it correct to use $rootScope and $broadcast or should there be a dependency between the myLayers directive and the myGoogleMap directive?
Furthermore, I've read about when to use controller, link and compile, yet I don't know the right way to use them here. My guess is that myGoogleMap should define its API in a controller and that myLayers should be dependent on myGoogleMap.
The example I wrote here works fine in my application. I'm looking for guidance on how to do it right and to understand what I did wrong here.
Thanks, Rotem
There are some ways for directives to cooperate/communicate
If one is a sibling or child of the other, you can use require. E.g. for this template:
<dir1>
<dir2></dir2>
</dir1>
Use this code:
app.directive('dir1', function() {
return {
...
controller: function() {
// define api here
}
};
});
app.directive('dir2', function() {
return {
...
require: '^dir1',
link: function(scope, elem, attrs, dir1Controller) {
// use dir1 api here
}
};
});
A service, used by both directives to communicate. This is easy and works well if the directives can only be instantiated once per view.
Using $broadcast/$emit on the $rootScope (there is a slight difference: $broadcast will "flood" the scope hierarchy, possibly affecting performance; $emit will only call listeners on the $rootScope, but this means you have to do $rootScope.$on() and then remember to deregister the listener when the current scope is destroyed - which means more code). This approach is good for decoupling components. It may become tricky in debugging, i.e. to find where that event came from (as with all event-based systems).
Other
controller, link and compile
Very short:
Use the controller to define the API of a directive, and preferably to define most its logic. Remember the element, the attributes and any transclude function are available to the controller as $element, $attrs and $transclude respectively. So the controller can, in most cases, replace the link function. Also remember that, unlike the link function, the controller is elligible for dependency injection. (However you can still do dependency injection at the directive level, so, after all, the link function can also access dependencies.)
Use the link function to access required controllers (see case 1 above). Or, if you are feeling lazy, to define the directive's logic. I think the controller is cleaner though.
Use the compile function... rarely :) When you need very special transformations to the template (repetition is the first thing that comes to mind - see ng-repeat) or other mystical stuff. I use directives all the time, about 1% of them needs a compile function.
My guess is that myGoogleMap should define its API in the controller and that myLayers should be dependent on myGoogleMap
The question is how will you communicate this API using events? You probably need only to create an API in a custom event object. The listeners of your event will be using that custom event. If so, the controller does not really need to define an API.
As a bottom line, I am perfectly OK with using events in your case.
Generally communication between directives should be handled via controllers and using the require property on the directive definition object.
If we re-work your first directive we can simply add that method to the controller:
directive('myGoogleMap', function () {
return {
template: '<div id="map" />',
controller: function ($scope) {
var _this = this;
//Part of this directives API
_this.addMarkers = function(arg){
//Do stuff
}
}
};
});
Now we can require this controller in another directive, but one little known features is that you can actually require an array of directives. One of those directives can even be yourself.
All of them will be passed, in order, as an array to your link function:
directive('myLayers', function () {
return {
templateUrl: 'someLayersHtml.html',
controller: function ($http, $scope, $rootScore) {
// Some get layers of markers functionality
},
// Require ourselves
// The '?' makes it optional
require: ['myLayers', '?myGoogleMap'],
link: function(scope, elem, attrs, ctrls){
var myLayersCtrl = ctrls[0];
var myGoogleMapCtrl = ctrls[1];
//something happens
if(myGoogleMapCtrl) {
myGoogleMapCtrl.addMarkers(markers);
}
}
};
});
Now you can communicate explicitly opt-in by using the ? which makes the controller optional.
In order for that to work, you have to define both directives in the same module, i.e.:
var module = angular.module('myModule');
module.directive('myGoogleMap', function(){
template: '<div id="map" />',
controller: function($scope){
// some initializations
// Listen to event for adding markers
$scope.$on('addMarkersEvent', function(e, data){
// do something
}
}
}
module.directive('myLayers', function() {
templateUrl: 'someLayersHtml.html',
controller: function($http, $scope, $rootScore){
// Some get layers of markers functionality
// On specific layer click, get markers and:
$rootScope.broadcast('addMarkersEvent', {
data: myMarkers
});
}
});
Read more here.
EDIT:
Sorry i didn't understand your question, but according to your comment, quoting from the AngularJs Best Practices:
Only use .$broadcast(), .$emit() and .$on() for atomic events
that are relevant globally across the entire app (such as a user
authenticating or the app closing). If you want events specific to
modules, services or widgets you should consider Services, Directive
Controllers, or 3rd Party Libs
$scope.$watch() should replace the need for events
Injecting services and calling methods directly is also
useful for direct communication
Directives are able to directly communicate with each other through directive-controllers
You have already highlight one may for the directives to communicate using rootscope.
Another way directive can communicate if they are defined on the same html hierarchy is by use directive controller function. You have highlighted that too in your question. The way it would be done is (assuming myGoogleMap is defined on parent html), the two directive definitions become:
angular.module('googleMapModule', [])
.directive('myGoogleMap', function () {
template: '<div id="map" />',
controller: function ($scope) {
this.addMarkersEvent = function (data) {}
// some initializations
}
angular.module('layersDirective', [])
.directive('myLayers', function ($http, $rootScope) {
templateUrl: 'someLayersHtml.html',
require: '^myGoogleMap',
link: function (scope, element, attrs, myGoogleMapController) {
$scope.doWork = function () {
myGoogleMapController.addMarkersEvent(data);
}
}
});
Here you use the require property on the child directive. Also instead of using child controller all the functionality in the child directive is now added to link function. This is because the child link function has access to parent directive controller.
Just a side note, add both directives to a single module (Update: Actually you can have the directives in different modules, as long as there are being referenced in the main app module.)

Angular directives - when and how to use compile, controller, pre-link and post-link [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 8 years ago.
Improve this question
When writing an Angular directive, one can use any of the following functions to manipulate the DOM behaviour, contents and look of the element on which the directive is declared:
compile
controller
pre-link
post-link
There seem to be some confusion as for which function should one use. This question covers:
Directive basics
How to declare the various functions?
What is the difference between a source template and an instance template?
In which order the directive functions are executed?
What else happens between these function calls?
Function nature, do's and dont's
Compile
Controller
Pre-link
Post-link
Related questions:
Directive: link vs compile vs controller.
Difference between the 'controller', 'link' and 'compile' functions when defining an angular.js directive.
What is the difference between compile and link function in angularjs.
Difference between the pre-compile and post-compile element in AngularJS directives?.
Angular JS Directive - Template, compile or link?.
post link vs pre link in Angular js directives.
In which order the directive functions are executed?
For a single directive
Based on the following plunk, consider the following HTML markup:
<body>
<div log='some-div'></div>
</body>
With the following directive declaration:
myApp.directive('log', function() {
return {
controller: function( $scope, $element, $attrs, $transclude ) {
console.log( $attrs.log + ' (controller)' );
},
compile: function compile( tElement, tAttributes ) {
console.log( tAttributes.log + ' (compile)' );
return {
pre: function preLink( scope, element, attributes ) {
console.log( attributes.log + ' (pre-link)' );
},
post: function postLink( scope, element, attributes ) {
console.log( attributes.log + ' (post-link)' );
}
};
}
};
});
The console output will be:
some-div (compile)
some-div (controller)
some-div (pre-link)
some-div (post-link)
We can see that compile is executed first, then controller, then pre-link and last is post-link.
For nested directives
Note: The following does not apply to directives that render their children in their link function. Quite a few Angular directives do so (like ngIf, ngRepeat, or any directive with transclude). These directives will natively have their link function called before their child directives compile is called.
The original HTML markup is often made of nested elements, each with its own directive. Like in the following markup (see plunk):
<body>
<div log='parent'>
<div log='..first-child'></div>
<div log='..second-child'></div>
</div>
</body>
The console output will look like this:
// The compile phase
parent (compile)
..first-child (compile)
..second-child (compile)
// The link phase
parent (controller)
parent (pre-link)
..first-child (controller)
..first-child (pre-link)
..first-child (post-link)
..second-child (controller)
..second-child (pre-link)
..second-child (post-link)
parent (post-link)
We can distinguish two phases here - the compile phase and the link phase.
The compile phase
When the DOM is loaded Angular starts the compile phase, where it traverses the markup top-down, and calls compile on all directives. Graphically, we could express it like so:
It is perhaps important to mention that at this stage, the templates the compile function gets are the source templates (not instance template).
The link phase
DOM instances are often simply the result of a source template being rendered to the DOM, but they may be created by ng-repeat, or introduced on the fly.
Whenever a new instance of an element with a directive is rendered to the DOM, the link phase starts.
In this phase, Angular calls controller, pre-link, iterates children, and call post-link on all directives, like so:
What else happens between these function calls?
The various directive functions are executed from within two other angular functions called $compile (where the directive's compile is executed) and an internal function called nodeLinkFn (where the directive's controller, preLink and postLink are executed). Various things happen within the angular function before and after the directive functions are called. Perhaps most notably is the child recursion. The following simplified illustration shows key steps within the compile and link phases:
To demonstrate the these steps, let's use the following HTML markup:
<div ng-repeat="i in [0,1,2]">
<my-element>
<div>Inner content</div>
</my-element>
</div>
With the following directive:
myApp.directive( 'myElement', function() {
return {
restrict: 'EA',
transclude: true,
template: '<div>{{label}}<div ng-transclude></div></div>'
}
});
Compile
The compile API looks like so:
compile: function compile( tElement, tAttributes ) { ... }
Often the parameters are prefixed with t to signify the elements and attributes provided are those of the source template, rather than that of the instance.
Prior to the call to compile transcluded content (if any) is removed, and the template is applied to the markup. Thus, the element provided to the compile function will look like so:
<my-element>
<div>
"{{label}}"
<div ng-transclude></div>
</div>
</my-element>
Notice that the transcluded content is not re-inserted at this point.
Following the call to the directive's .compile, Angular will traverse all child elements, including those that may have just been introduced by the directive (the template elements, for instance).
Instance creation
In our case, three instances of the source template above will be created (by ng-repeat). Thus, the following sequence will execute three times, once per instance.
Controller
The controller API involves:
controller: function( $scope, $element, $attrs, $transclude ) { ... }
Entering the link phase, the link function returned via $compile is now provided with a scope.
First, the link function create a child scope (scope: true) or an isolated scope (scope: {...}) if requested.
The controller is then executed, provided with the scope of the instance element.
Pre-link
The pre-link API looks like so:
function preLink( scope, element, attributes, controller ) { ... }
Virtually nothing happens between the call to the directive's .controller and the .preLink function. Angular still provide recommendation as to how each should be used.
Following the .preLink call, the link function will traverse each child element - calling the correct link function and attaching to it the current scope (which serves as the parent scope for child elements).
Post-link
The post-link API is similar to that of the pre-link function:
function postLink( scope, element, attributes, controller ) { ... }
Perhaps worth noticing that once a directive's .postLink function is called, the link process of all its children elements has completed, including all the children's .postLink functions.
This means that by the time .postLink is called, the children are 'live' are ready. This includes:
data binding
transclusion applied
scope attached
The template at this stage will thus look like so:
<my-element>
<div class="ng-binding">
"{{label}}"
<div ng-transclude>
<div class="ng-scope">Inner content</div>
</div>
</div>
</my-element>
How to declare the various functions?
Compile, Controller, Pre-link & Post-link
If one is to use all four function, the directive will follow this form:
myApp.directive( 'myDirective', function () {
return {
restrict: 'EA',
controller: function( $scope, $element, $attrs, $transclude ) {
// Controller code goes here.
},
compile: function compile( tElement, tAttributes, transcludeFn ) {
// Compile code goes here.
return {
pre: function preLink( scope, element, attributes, controller, transcludeFn ) {
// Pre-link code goes here
},
post: function postLink( scope, element, attributes, controller, transcludeFn ) {
// Post-link code goes here
}
};
}
};
});
Notice that compile returns an object containing both the pre-link and post-link functions; in Angular lingo we say the compile function returns a template function.
Compile, Controller & Post-link
If pre-link isn't necessary, the compile function can simply return the post-link function instead of a definition object, like so:
myApp.directive( 'myDirective', function () {
return {
restrict: 'EA',
controller: function( $scope, $element, $attrs, $transclude ) {
// Controller code goes here.
},
compile: function compile( tElement, tAttributes, transcludeFn ) {
// Compile code goes here.
return function postLink( scope, element, attributes, controller, transcludeFn ) {
// Post-link code goes here
};
}
};
});
Sometimes, one wishes to add a compile method, after the (post) link method was defined. For this, one can use:
myApp.directive( 'myDirective', function () {
return {
restrict: 'EA',
controller: function( $scope, $element, $attrs, $transclude ) {
// Controller code goes here.
},
compile: function compile( tElement, tAttributes, transcludeFn ) {
// Compile code goes here.
return this.link;
},
link: function( scope, element, attributes, controller, transcludeFn ) {
// Post-link code goes here
}
};
});
Controller & Post-link
If no compile function is needed, one can skip its declaration altogether and provide the post-link function under the link property of the directive's configuration object:
myApp.directive( 'myDirective', function () {
return {
restrict: 'EA',
controller: function( $scope, $element, $attrs, $transclude ) {
// Controller code goes here.
},
link: function postLink( scope, element, attributes, controller, transcludeFn ) {
// Post-link code goes here
},
};
});
No controller
In any of the examples above, one can simply remove the controller function if not needed. So for instance, if only post-link function is needed, one can use:
myApp.directive( 'myDirective', function () {
return {
restrict: 'EA',
link: function postLink( scope, element, attributes, controller, transcludeFn ) {
// Post-link code goes here
},
};
});
What is the difference between a source template and an instance template?
The fact that Angular allows DOM manipulation means that the input markup into the compilation process sometimes differ from the output. Particularly, some input markup may be cloned a few times (like with ng-repeat) before being rendered to the DOM.
Angular terminology is a bit inconsistent, but it still distinguishes between two types of markups:
Source template - the markup to be cloned, if needed. If cloned, this markup will not be rendered to the DOM.
Instance template - the actual markup to be rendered to the DOM. If cloning is involved, each instance will be a clone.
The following markup demonstrates this:
<div ng-repeat="i in [0,1,2]">
<my-directive>{{i}}</my-directive>
</div>
The source html defines
<my-directive>{{i}}</my-directive>
which serves as the source template.
But as it is wrapped within an ng-repeat directive, this source template will be cloned (3 times in our case). These clones are instance template, each will appear in the DOM and be bound to the relevant scope.
Compile function
Each directive's compile function is only called once, when Angular bootstraps.
Officially, this is the place to perform (source) template manipulations that do not involve scope or data binding.
Primarily, this is done for optimisation purposes; consider the following markup:
<tr ng-repeat="raw in raws">
<my-raw></my-raw>
</tr>
The <my-raw> directive will render a particular set of DOM markup. So we can either:
Allow ng-repeat to duplicate the source template (<my-raw>), and then modify the markup of each instance template (outside the compile function).
Modify the source template to involve the desired markup (in the compile function), and then allow ng-repeat to duplicate it.
If there are 1000 items in the raws collection, the latter option may be faster than the former one.
Do:
Manipulate markup so it serves as a template to instances (clones).
Do not
Attach event handlers.
Inspect child elements.
Set up observations on attributes.
Set up watches on the scope.
Controller function
Each directive's controller function is called whenever a new related element is instantiated.
Officially, the controller function is where one:
Defines controller logic (methods) that may be shared between controllers.
Initiates scope variables.
Again, it is important to remember that if the directive involves an isolated scope, any properties within it that inherit from the parent scope are not yet available.
Do:
Define controller logic
Initiate scope variables
Do not:
Inspect child elements (they may not be rendered yet, bound to scope, etc.).
Post-link function
When the post-link function is called, all previous steps have taken place - binding, transclusion, etc.
This is typically a place to further manipulate the rendered DOM.
Do:
Manipulate DOM (rendered, and thus instantiated) elements.
Attach event handlers.
Inspect child elements.
Set up observations on attributes.
Set up watches on the scope.
Pre-link function
Each directive's pre-link function is called whenever a new related element is instantiated.
As seen previously in the compilation order section, pre-link functions are called parent-then-child, whereas post-link functions are called child-then-parent.
The pre-link function is rarely used, but can be useful in special scenarios; for example, when a child controller registers itself with the parent controller, but the registration has to be in a parent-then-child fashion (ngModelController does things this way).
Do not:
Inspect child elements (they may not be rendered yet, bound to scope, etc.).

How do I assign an attribute to ng-controller in a directive's template in AngularJS?

I have a custom attribute directive (i.e., restrict: "A") and I want to pass two expressions (using {{...}}) into the directive as attributes. I want to pass these attributes into the directive's template, which I use to render two nested div tags -- the outer one containing ng-controller and the inner containing ng-include. The ng-controller will define the controller exclusively used for the template, and the ng-include will render the template's HTML.
An example showing the relevant snippets is below.
HTML:
<div ng-controller="appController">
<custom-directive ctrl="templateController" tmpl="template.html"></custom-directive>
</div>
JS:
function appController($scope) {
// Main application controller
}
function templateController($scope) {
// Controller (separate from main controller) for exclusive use with template
}
app.directive('customDirective', function() {
return {
restrict: 'A',
scope: {
ctrl: '#',
tmpl: '#'
},
// This will work, but not what I want
// Assigning controller explicitly
template: '<div ng-controller="templateController">\
<div ng-include="tmpl"></div>\
</div>'
// This is what I want, but won't work
// Assigning controller via isolate scope variable from attribute
/*template: '<div ng-controller="ctrl">\
<div ng-include="tmpl"></div>\
</div>'*/
};
});
It appears that explicitly assigning the controller works. However, I want to assign the controller via an isolate scope variable that I obtain from an attribute located inside my custom directive in the HTML.
I've fleshed out the above example a little more in the Plunker below, which names the relevant directive contentDisplay (instead of customDirective from above). Let me know in the comments if this example needs more commented clarification:
Plunker
Using an explicit controller assignment (uncommented template code), I achieve the desired functionality. However, when trying to assign the controller via an isolate scope variable (commented template code), it no longer works, throwing an error saying 'ctrl' is not a function, got string.
The reason why I want to vary the controller (instead of just throwing all the controllers into one "master controller" as I've done in the Plunker) is because I want to make my code more organized to maintain readability.
The following ideas may be relevant:
Placing the ng-controller tags inside the template instead of wrapping it around ng-include.
Using one-way binding ('&') to execute functions instead of text binding ('#').
Using a link function instead of / in addition to an isolate scope.
Using an element/class directive instead of attribute directive.
The priority level of ng-controller is lower than that of ng-include.
The order in which the directives are compiled / instantiated may not be correct.
While I'm looking for direct solutions to this issue, I'm also willing to accept workarounds that accomplish the same functionality and are relatively simple.
I don't think you can dynamically write a template key using scope, but you certainly do so within the link function. You can imitate that quite succinctly with a series of built-in Angular functions: $http, $controller, $compile, $templateCache.
Plunker
Relevant code:
link: function( scope, element, attrs )
{
$http.get( scope.tmpl, { cache: $templateCache } )
.then( function( response ) {
templateScope = scope.$new();
templateCtrl = $controller( scope.ctrl, { $scope: templateScope } );
element.html( response.data );
element.children().data('$ngControllerController', templateCtrl);
$compile( element.contents() )( templateScope );
});
}
Inspired strongly by this similar answer.

angularjs custom directive isolated scope one way data binding doesn't work

i am a new to angularjs, I read some literature and followed a lot of tutorials, but i am still have the feeling that i completely confused.
My current issue is with custom directive and isolated scopes. All i trying to do is pass "strings" with # binding to my directives that use isolated scopes and I can't understand what am i doing wrong. Specifically WHY when i use template everything just works fine and when the template already in the DOM the one way data binding doesn't work.
JSBin fiddle link
major parts from my code:
HTML
<div my-directive my-title="TITLE ONE WAY Data Binding">
<div>
<div>This directive is <span style="color:red;">NOT using template</span></div>
<div>
$scope.title = <small><pre>{{title}}</pre></small>
</div>
</div>
</div>
<div my-directive-with-template my-title="TITLE ONE WAY Data Binding"
>
<!-- this directive use a template -->
</div>
JS
var app = angular.module('app', []);
app.directive('myDirective', function() {
return {
restrict: 'AE',
scope:{
title: "#myTitle"
},
link: function(scope, ele, attrs, c) {
console.log('non template directive link:',scope.title,attrs.myTitle);
},
controller:['$scope', function($scope){
console.log('non template directive controller:',$scope.title);
}]
};
});
app.directive('myDirectiveWithTemplate', function() {
return {
restrict: 'AE',
scope:{
title: "#myTitle"
},
link: function(scope, ele, attrs, c) {
console.log('using template directive link:',scope.title,attrs.myTitle);
},
controller:['$scope', function($scope){
console.log('using template directive link:',$scope.title);
}],
template:'<div><div>This directive is using template</div><div>$scope.title = <small><pre>"{{title}}"</pre></small></div></div>',
replace:true
};
});
JSBin fiddle link
In your non-template scenario the title is not being bound to any scope and therefore not showing anything.
What you call the DOM template is really HTML outside the directive that has no access to it's isolated scope. You could embed this div inside a controller and then title could be bound to the controller's $scope.title
For what I understand it only makes sense to create an isolated scope to make it available to the directive's template.
Clarification
Isolated scopes allow the directive to have state independent of the parent scope (avoiding it's pollution) and also avoiding sharing this state with sibling directives.
Supposing you're creating this directive to reuse that piece of UI somewhere else in your code, you start by creating its template with the shared HTML.
Ok, but you need to go a bit further and parameterize it passing some data to it.
You can then use attributes on the directive to communicate with the outside (parent scope, or just to pass static data).
The directive's template can now bind to this data without needing to have any knowledge of it's "outside world", and it's done through it's isolated scope.
Conclusion, why create an isolated scope, if not to provide the template with this data?
Hope I've made this a bit clear :)
Now after thinking a bit about my affirmation... well you could also create a directive without any template, by using the compile or link function and do it manually through DOM manipulation. And in this case it might make sense to have an isolated scope for the reasons presented above :)

Access controller scope from directive

I've created a simple directive that displays sort column headers for a <table> I'm creating.
ngGrid.directive("sortColumn", function() {
return {
restrict: "E",
replace: true,
transclude: true,
scope: {
sortby: "#",
onsort: "="
},
template: "<span><a href='#' ng-click='sort()' ng-transclude></a></span>",
link: function(scope, element, attrs) {
scope.sort = function () {
// I want to call CONTROLLER.onSort here, but how do I access the controller scope?...
scope.controllerOnSort(scope.sortby);
};
}
};
});
Here's an example of some table headers being created:
<table id="mainGrid" ng-controller="GridCtrl>
<thead>
<tr>
<th><sort-column sortby="Name">Name</sort-column></th>
<th><sort-column sortby="DateCreated">Date Created</sort-column></th>
<th>Hi</th>
</tr>
</thead>
So when the sort column is clicked I want to fire the onControllerSort function on my grid controller.. but I'm stuck! So far the only way I've been able to do this is for each <sort-column>, add attributes for the "onSort" and reference those in the directive:
<sort-column onSort="controllerOnSort" sortby="Name">Name</sort-column>
But that's not very nice since I ALWAYS want to call controllerOnSort, so plumbing it in for every directive is a bit ugly. How can I do this within the directive without requiring unnecesary markup in my HTML? Both the directive and controller are defined within the same module if that helps.
Create a second directive as a wrapper:
ngGrid.directive("columnwrapper", function() {
return {
restrict: "E",
scope: {
onsort: '='
}
};
});
Then you can just reference the function to call once in the outer directive:
<columnwrapper onsort="controllerOnSort">
<sort-column sortby="Name">Name</sort-column>
<sort-column sortby="DateCreated">Date Created</sort-column>
</columnwrapper>
In the "sortColumn" directive you can then call that referenced function by calling
scope.$parent.onsort();
See this fiddle for a working example: http://jsfiddle.net/wZrjQ/1/
Of course if you don't care about having hardcoded dependencies, you could also stay with one directive and just call the function on the parent scope (that would then be the controller in question) through
scope.$parent.controllerOnSort():
I have another fiddle showing this: http://jsfiddle.net/wZrjQ/2
This solution would have the same effect (with the same criticism in regard to hard-coupling) as the solution in the other answer (https://stackoverflow.com/a/19385937/2572897) but is at least somewhat easier than that solution. If you couple hard anyway, i don't think there is a point in referencing the controller as it would most likely be available at $scope.$parent all the time (but beware of other elements setting up a scope).
I would go for the first solution, though. It adds some little markup but solves the problem and maintains a clean separation. Also you could be sure that $scope.$parent matches the outer directive if you use the second directive as a direct wrapper.
The & local scope property allows the consumer of a directive to pass in a function that the directive can invoke.
See details here.
Here is a answer to a similar question, which shows how to pass argument in the callback function from the directive code.
In your directive require the ngController and modify the link function as:
ngGrid.directive("sortColumn", function() {
return {
...
require: "ngController",
...
link: function(scope, element, attrs, ngCtrl) {
...
}
};
});
What you get as ngCtrl is your controller, GridCtrl. You dont get its scope though; you would have to do something in the lines of:
xxxx.controller("GridCtrl", function($scope, ...) {
// add stuff to scope as usual
$scope.xxxx = yyyy;
// Define controller public API
// NOTE: USING this NOT $scope
this.controllerOnSort = function(...) { ... };
});
Call it from the link function simply as:
ngCtrl.controllerOnSort(...);
Do note that this require will get the first parent ngController. If there is another controller specified between GridCtrl and the directive, you will get that one.
A fiddle that demonstrates the principle (a directive accessing a parent ng-controller with methods): http://jsfiddle.net/NAfm5/1/
People fear that this solution may introduce unwanted tight coupling. If this is indeed a concern, it can be addressed as:
Create a directive that will be side-by-side with the controller, lets call it master:
<table id="mainGrid" ng-controller="GridCtrl" master="controllerOnSort()">
This directive references the desired method of the controller (thus: decoupling).
The child directive (sort-column in your case) requires the master directive:
require: "^master"
Using the $parse service the specified method can be called from a member method of the master controller. See updated fiddle implementing this principle: http://jsfiddle.net/NAfm5/3/
There is another way to do this, although given my relative lack of experience I can't speak for the fitness of such a solution. I will pass it along anyhow just for informational purposes.
In your column, you create a scope variable attribute:
<sort-column data-sortby="sortby">Date Created</sort-column>
Then in your controller you define the scope variable:
$scope.sortby = 'DateCreated' // just a default sort here
Then add your sort function in controller:
$scope.onSort = function(val) {
$scope.sortby = val;
}
Then in your markup wire up ng-click:
<sort-column data-sortby="sortby" ng-click="onSort('DateCreated')">Date Created</sort-column>
Then in your directive you add the sortby attribute to directive scope:
scope: {
sortby: '=' // not sure if you need
}
And in your "link:" function add a $watch:
scope.$watch('sortby', function () {
... your sort logic here ...
}
The beauty of this approach IMO is that your directive is decoupled completely, you don't need to call back to onSort from the directive because you don't really leave onSort in the controller during that part of the execution path.
If you needed to tell your controller to wait for the sort to finish you could define an event in the controller:
$scope.$on("_sortFinished", function(event, message){
..do something...
});
Then in your directive simply emit the event then the process is done:
$scope.$emit('_sortFinished');
There's other ways to do that, and this kind of adds some tight-ish coupling because your controller has to listen for. and your directive has to emit a specific even... but that may not be an issue for you since they are closely related anyhow.
Call me crazy, but it seems easier to just get the controller from the element via the inbuilt method for that, rather than fiddling with require:
var mod = angular.module('something', []).directive('myDir',
function () {
return {
link: function (scope, element) {
console.log(element.controller('myDir'));
},
controller: function () {
this.works = function () {};
},
scope: {}
}
}
);
http://plnkr.co/edit/gY4rP0?p=preview

Resources