angular.js Getting the element from inside $evalAsync in directive - angularjs

I'm finding that I'm using scope.$evalAsync inside a directive quite a lot. Mainly to do DOM stuff/jquery plugins that need all the template {{vars}} compiled.
I can get at the scope object from inside $evalAsync but not the element. In latest case in question, I'm manipulating an element that gets rendered with an ngRepeat. I'm currently getting the element by composing a jquery selector based on the scope object e.g.
scope.$evalAsync(function (scope) {
$("#item-" + scope.id).runJQplugin();
})
Although this works, to me it would be more intuitive to be able to do this
scope.$evalAsync(function (scope,element) {
element.runJQplugin();
})
Am I approaching this right or have I misunderstood something fundamental with directives?

You always have access to the element from the link and the controller of a directive through the closure scope. So in link function:
link: function(scope, elem, attrs) {
...
scope.$evalAsync(function(scope) {
elem.runJQplugin();
});
...
},
Controller (you need to specify the special $element dependency):
controller: ["$scope", "$element", function($scope, $element) {
...
scope.$evalAsync(function(scope) {
$element.runJQplugin();
});
...
}],

Related

get scope in directive

everybody. I am new to AngularJS and find it very interesting, but I am a bit unclear about the following situation.
app.controller("myCtrl", ['$scope', '$http', '$filter', function ($scope, http, filter)
{
$http({
method: CTHocUri,
url: 'get',
async: true,
}).then(function (response) {
$scope.CTH = response.data; //response.data=two Object
})
}])
app.directive("myCustom1",['$http', function ($compile,$http) {
return {
link: function (scope, element, attr) {
console.log(scope.CTH); // I can't get... scope.CTH=undefined
}
}])
I can't get value scope.CTH. ??
There is a VERY simple way to SEE what the issue is:
In your html, merely surround your directive with an ng-if conditional based on CTH:
<span ng-if="CTH">
<my-custom-1></my-custom-1>
</span>
That's it.
What this does is that your directive will only be born/instantiated when CTH is set to non-null/non-undefined, i.e. when $http returns asynchronously. With this, your code will work. As such, there is no need for watching or broadcasting for this type of simple serialization of asynchronous events when you can simply leverage Angular's built-in '$watch's.
NOTE 1: I do not know what your architecture is and am not suggesting what you need to do. I am merely showing you why your code won't work and how you have been caught in a simple asynchronicity trap.
NOTE 2: I assume your directive is 'as -is'. In other words you have access to the parent's scope (i.e. the controller's scope). If your directive's scope were isolated (i.e. you had a scope:{..(attrs)..} defined in the directive) you will not have 'simple' access to the parent scope. Your code will be different--eg you can pass bits and pieces of your scope to the directive attrs. However, the ng-if will still work since it is on the controller's scope.
I hope this helps.
The directive and the controller are two completely different entities. If it helps you can think of them as different classes. They will not share the same scope.
You could create an isolated scope on the directive and pass the CTH variable into it. Conceptually something like this:
app.directive("myCustom1",['$http', function ($compile,$http) {
return {
scope { cth : "=" },
link: function (scope, element, attr) {
console.log(scope.cth);
}
Then in your HTML, do something like this:
<div ng-controller="myCtrl">
<my-Custom1 cth="CTH">
</div>
when the directive initializes, the scope.CTH is still not initialized since its initialization accurses inside an $http call.
one way to overcome this is to broadcast and event from the controller and catch it from inside the directive. see this plnkr and angularjs scope's docs
app.controller('MainCtrl', function($scope, $timeout) {
$scope.name = 'World';
$timeout(function() {
$scope.test = "test";
$scope.$broadcast('MyEvent')
}, 500);
});
app.directive('test', function() {
return {
link: function(scope, elm, attr) {
scope.$on('MyEvent', function() {
console.log(scope.test);
})
}
}
})

Angular: data not passing from controller to directive using isloated scope

The question title explain my problem i want to send data from a controller to directive so i can use the data in the directive controller or view.
Here is the controller code:
$scope.following = product.vendorId.isUserFollowing;
In the controller view:
<vas-follow following="{{following}}"></vas-follow>
following the property am trying to pass to the directive, the directive code:
.directive('vasFollow', vasFollow);
function vasFollow() {
var directive = {
restrict: "EA",
scope: {
following: '#'
},
link: link,
controller: vasFollowCtrl,
templateUrl: 'templates/directives/vasFollow.html',
};
return directive;
function link(scope, element, attrs) {
/* */
};
}
I tried first to use the following like so {{following}} in the directive view but it's not passing, also it is undefined in the directive controller.
I have read a lot of slimier issues but, i couldn't conclude why am having this problem.
Use ng-model for directive instead of your own replacement for it
Remove {{}} from assignment to share link to variable instead of just evaluated value
And please, use div or common DOM element instead of exact naming directive - it have side-effects in I.E.

Simple Angular Directive

I'm attempting to write my first Angular directive to add pagination functionality to my app.
I'm keeping it simple for now and want to have the directive just share the scope of the controller.
In my controller, I have a scoped var called:
$scope.pages
Inside my directive's linking function, I can do:
console.dir($scope)
to see the contents of the controller's scope object, but if I try to access the pages property, it comes back as undefined.
What am I missing?
Thanks in advance.
myDirectives.directive("mdPagination", function(){
return {
template: '',
restrict: 'A',
priority: 1000,
scope: false,
link: function($scope, el, attrs){
console.dir($scope.pages); //returns undefined
el.text('hello world');
},
}
});
Edit: I'm aware there's pagination modules out there, but my needs are modest and I thought building my own would be an easy way to start learning how to build directives.
The problem you have is directives linking functions are run before controllers are, so you typically can't access scope values during this linking phase. There are a couple other ways to do this. The most immediate way to solve your problem is with $scope.$watch. So something like this should work:
$scope.watch('pages', function(pages) {
console.dir(pages);
});
This works just fine for lost of circumstances. I would also suggest decoupling your controller from the scope it is declared in a bit by using an attribute to pass in an expression (property name basically) for your watch. A simple example might look like this:
<div my-directive="myProperty">
link: function($scope, el, attrs) {
$scope.watch(attrs.myDirective, function(value) {
//do something you need to do with the value here
});
}
However as you add complexity to your directive you may want to give your directive it's own controller to manage state without directly calling watches. This controller can either use a child scope belonging to the directive itself with scope: true or just use the parent scope with scope: false. Your choice will likely depend upon need. Then you could have:
myDirectives.directive("mdPagination", function(){
return {
template: '',
restrict: 'A',
priority: 1000,
controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
console.dir($scope.pages);
}],
link: function($scope, el, attrs){
$element.text('hello world');
}
}
});
By default, the directive shares its scope with the controller. To do that, you must not having a "scope" attribute in your object, like that:
myDirectives.directive("mdPagination", function(){
return {
template: '',
restrict: 'A',
priority: 1000,
link: function($scope, el, attrs){
console.dir($scope.pages);
el.text('hello world');
},
}
});
#Sandro is right. You set the scope property in your directive to false which means that the scope will not inherit (or share) its properties with the controller. If you remove the false, it should work as you intend it.
The docs for how to use $scope are here: http://code.angularjs.org/1.2.0-rc.3/docs/api/ng.$rootScope.Scope
The developer guide for creating directives has a lot of good info about how to setup your directive so that it gives you the scope you want. Those docs are here: http://code.angularjs.org/1.2.0-rc.3/docs/guide/directive
Hope that helps.

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

Link vs compile vs controller

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.

Resources