AngularJS DOM Access On Init - angularjs

I wrote this simple jsfiddle in which I do (successfully) some basic addClass on a directive in a ng-repeat.
http://jsfiddle.net/rv6u2/5/
Now, my question is: which is the best (or intended) place to do such DOM manipulations:
A. In the directive?
B. In the controller?
Both possibilities are shown in my example.
Code:
var TestApp = angular.module("TestApp", ['ngResource']);
TestApp.directive('onLoad', function() {
return {
restrict: 'A',
link: function(scope, elm, attrs) {
elm.addClass('loaded'); // A: DOM manipulation in directive
scope.initMe(scope.$eval(attrs.onLoad2), elm); // B: DOM manipulation handled in controller
}
};
});
thanks in advance :)

NEVER manipulate the dom inside of controllers.
Controllers should just use services and update attributes of $scope. All DOM manipulation should be made by directives and(in some cases) services(e.g. $anchorScroll)
See the concepts of angularjs here
UPDATE: Example of the correct way here

A more "Angular way" of setting class loaded2 would be as follows (which avoids DOM manipulation inside the controller):
In the HTML, declare a model for the class (myClass):
<div ng-repeat="item in items" ng-model="item" on-load="initMe(item)" ng-class="myClass">
In the link function, just call the controller method:
scope.initMe()
In the controller, manipulate the model/$scope property:
$scope.initMe = function() {
$scope.myClass = "loaded2";
}
Changing the model/scope will automatically update the view.
This method is useful if you want to declare in the HTML that a class is being controlled by $scope property myClass. However, using elm.addClass() inside the linking function is more self-contained and easier to maintain (and I like that approach better).

Related

AngularJS: How a Directive without a Controller can work

just reading a write up from this link http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-6-using-controllers
it was hard me like novice in ng to understand their code sample. just tell me a example where people would write directive without controller ?
their code
(function() {
var app = angular.module('directivesModule');
app.directive('isolateScopeWithController', function () {
var controller = ['$scope', function ($scope) {
function init() {
$scope.items = angular.copy($scope.datasource);
}
init();
$scope.addItem = function () {
$scope.add();
//Add new customer to directive scope
$scope.items.push({
name: 'New Directive Controller Item'
});
};
}],
template = '<button ng-click="addItem()">Add Item</button><ul>' +
'<li ng-repeat="item in items">{{ ::item.name }}</li></ul>';
return {
restrict: 'EA', //Default in 1.3+
scope: {
datasource: '=',
add: '&',
},
controller: controller,
template: template
};
});
}());
Directive usage:
Attribute: <div isolate-scope-with-controller datasource="customers" add="addCustomer()"></div>
Element: <isolate-scope-with-controller datasource="customers" add="addCustomer()"></isolate-scope-with-controller>
How we can pass customer data directly to directive. basically we have model in controller and populate model and then pass that model data to directive via isolated scope or directive use controller scope. I am very confused the how above code can work, please help me to understand. thanks
The scenario that is being considered implies that the directive will be used in a part of the application, that already has a declared controller, the scope of which contains the properties datasource and add. In turn, new controllers will be instantiated for each of the instances of the directive and will have their own isolate scope.
In reality, it is much more common to create directives that do not have a controller, but rather use the link function. These directives can either rely on the parent controller, sometimes perform DOM manipulation, bind to JS events or simply serve as means to encapsulate part of your application.
You can find a good example of a directive that does not create its own controller here. It is taken from the Angular docs. You will find that it does not even belong to a parent scope in this case meaning that no controller is involved. In reality, an element like this would most probably report to a parent controller, which would then do something with the position.
You can read more about directives, the link function, and how directives work with controllers here.

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 :)

AngularJS - Find Element with attribute

I'm new to AngularJS. I've learned that I can find elements in the DOM using queries like the following:
var e = angular.element(document.querySelector('#id'));
var e = angular.element(elem.querySelector('.classname'));
This is useful for finding elements by ID, or by CSS class name. However, I need to be able to find an element using a different approach. I have an element that looks like the following:
<div my-directive class='myContainer'>...</div>
I can't query on 'myContainer' because of how much its reused. For that reason, I would like to find any element with the attribute 'my-directive'. How do I search the DOM and find any element that makes use of 'my-directive'?
Rather than querying the DOM for elements (which isn't very angular see "Thinking in AngularJS" if I have a jQuery background?) you should perform your DOM manipulation within your directive. The element is available to you in your link function.
So in your myDirective
return {
link: function (scope, element, attr) {
element.html('Hello world');
}
}
If you must perform the query outside of the directive then it would be possible to use querySelectorAll in modern browers
angular.element(document.querySelectorAll("[my-directive]"));
however you would need to use jquery to support IE8 and backwards
angular.element($("[my-directive]"));
or write your own method as demonstrated here Get elements by attribute when querySelectorAll is not available without using libraries?
You haven't stated where you're looking for the element. If it's within the scope of a controller, it is possible, despite the chorus you'll hear about it not being the 'Angular Way'. The chorus is right, but sometimes, in the real world, it's unavoidable. (If you disagree, get in touch—I have a challenge for you.)
If you pass $element into a controller, like you would $scope, you can use its find() function. Note that, in the jQueryLite included in Angular, find() will only locate tags by name, not attribute. However, if you include the full-blown jQuery in your project, all the functionality of find() can be used, including finding by attribute.
So, for this HTML:
<div ng-controller='MyCtrl'>
<div>
<div name='foo' class='myElementClass'>this one</div>
</div>
</div>
This AngularJS code should work:
angular.module('MyClient').controller('MyCtrl', [
'$scope',
'$element',
'$log',
function ($scope, $element, $log) {
// Find the element by its class attribute, within your controller's scope
var myElements = $element.find('.myElementClass');
// myElements is now an array of jQuery DOM elements
if (myElements.length == 0) {
// Not found. Are you sure you've included the full jQuery?
} else {
// There should only be one, and it will be element 0
$log.debug(myElements[0].name); // "foo"
}
}
]);
Your use-case isn't clear. However, if you are certain that you need this to be based on the DOM, and not model-data, then this is a way for one directive to have a reference to all elements with another directive specified on them.
The way is that the child directive can require the parent directive. The parent directive can expose a method that allows direct directive to register their element with the parent directive. Through this, the parent directive can access the child element(s). So if you have a template like:
<div parent-directive>
<div child-directive></div>
<div child-directive></div>
</div>
Then the directives can be coded like:
app.directive('parentDirective', function($window) {
return {
controller: function($scope) {
var registeredElements = [];
this.registerElement = function(childElement) {
registeredElements.push(childElement);
}
}
};
});
app.directive('childDirective', function() {
return {
require: '^parentDirective',
template: '<span>Child directive</span>',
link: function link(scope, iElement, iAttrs, parentController) {
parentController.registerElement(iElement);
}
};
});
You can see this in action at http://plnkr.co/edit/7zUgNp2MV3wMyAUYxlkz?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