Angular directive DOM manipulation dependent on scope variables - angularjs

I have read that DOM manipulation in angular should be done in the compile function of a directive, and not in the pre-link/post-link/controller. The compile function does not have access to the scope.
My problem is that I want to do DOM manipulation that is dependent on scope variables. For example, I have a list that I am passing into the directive. Within the directive, I am creating a custom select with the list items within it. Where is the right place to manipulate the DOM in this case?
Note that I am not using ng-repeat - I have found it very slow when the list becomes large.

I don't know where you have read "DOM manipulation in angular should be done in the compile function of a directive". That contradicts the advice of the AngularJS team.
Creating a Directive that Manipulates the DOM
Directives that want to modify the DOM typically use the link option to register DOM listeners as well as update the DOM.
-- AngularJS Developer Guide - Directives - DOM Manipulation
The built-in directives, ng-repeat, ng-if, ng-when, etc. all do their DOM manipulation in the link function.
compile
The compile function deals with transforming the template DOM. Since most directives do not do template transformation, it is not used often.
-- AngularJS $compile Service API Reference -- compile

Related

How does Angular-1.5.x component tree's loading works?

I read document from AngualrJS : https://docs.angularjs.org/guide/component
And when I am reading to "Example of a component tree" section, I got confused about how a component tree's loading works, because there is nowhere to find the loading order.
How angular find the root directive and nested directive (may be in a template)? and Which component should start work first? I mean, If a nested component() is called earlier than its directive's appearance, will it be called later when its directive appears? How angular knows the appropriate component to call when a directive/template load? Or just iterate all components method? Or load all components first, then according index.html to form a component hierarchy, then call it properly?
Is there anybody kindly explain? thanks a lot!
Thanks estus!, your anwser is really helpful! According to AngularJS : How does the HTML compiler arrange the order for compiling? and Pete Bacon Darwin's example :
Just in my opinion:
Angular call all components comiple() method first to complete whole template loading, then call their controller() methods regard to directives hierarchy.
And this comes from : How directives are compiled
It's important to note that Angular operates on DOM nodes rather than
strings. Usually, you don't notice this restriction because when a
page loads, the web browser parses HTML into the DOM automatically.
HTML compilation happens in three phases:
$compile traverses the DOM and matches directives.
If the compiler finds that an element matches a directive, then the directive is added to the list of directives that match the DOM
element. A single element may match multiple directives.
Once all directives matching a DOM element have been identified, the compiler sorts the directives by their priority. Each
directive's compile functions are executed. Each compile function
has a chance to modify the DOM. Each compile function returns a
link function. These functions are composed into a "combined" link
function, which invokes each directive's returned link function.
$compile links the template with the scope by calling the combined linking function from the previous step. This in turn will
call the linking function of the individual directives, registering
listeners on the elements and setting up $watchs with the
scope as each directive is configured to do.

How can an Angular directive have a 'native' parent state when it is compiled elsewhere?

I use ui-router to create routes with multiple HTML pages.
I have custom "target" directives within these pages that use $state.current in various ways.
But I also have other custom "lookup" directives in other states which load these HTML "templates", find these "target" directives, compile them and insert them into the DOM.
The problem I am having is that when the HTML content is compiled in these second directives, $state.current obviously refers to whatever state the application is in when it is compiled, whereas I would like the directive to compile as if it was in its "native" state.
Is there any (easy-ish!) way to get a reference to the target directive's "native" state? i.e. the state connected to the .HTML file which the directive is in? Is there a method (angular, jquery, native js or anything else) to get from the directive to the HTML template file? Then I could do a reverse lookup on the state objects. element.ownerDocument can get the URL of the current state, but not the HTML file of the template.
Alternatively, if the second directive had a reference to the "target" state, how should I modify the "target" directive so that it takes a state reference when it is compiled in both scenarios? Something in the compile function perhaps?? - but the docs don't seem to cover this kind of use case... I could do with a pointer in the right direction.
I hope that all makes sense. I have looked around for similar answers, but I guess this is an unusual use case? I'll knock up a Plunker shortly to help illustrate...
When you compile HTML using $compile, you pass in a scope object. Instead of using $state.current within the directive, could you attach the "state" you need to the scope object that you're passing in?

Bind an externally loaded DOM element to Angular's scope

I'm using Google Maps within an Angular app. As per Google Maps's API, I pass a string to a google.maps function containing HTML, and the library displays it on the map as an InfoView.
I'm adding a button to this view, and I want this view to be bound to a controller in my Angular app, however, even if I pass <div ng-controller="MyController">...</div>, when the Maps library attaches it to the DOM, Angular is not notified.
How can I force the compilation of this element (over which I have very little control: just passing a string)?
For AngularJS to "notice" the directive, it'd need to compile the element, which is not done automatically except when bootstrapping the application. So either the API needs to allow you to pass DOM Element to it (and you need to $compile and link it before you pass it on), or you need to find the element after it is added to DOM and then $compile and link it. AFAIK, if you cannot do either of those, then what you're asking is impossible.
If you manage to get hold of the element, compiling it is as simple as calling
$compile(element)($scope);
where $scope is the scope you want to link it to (possibly even $rootScope).

Access Angular Controller Defined Element

Is there a way to access the div which is in the controller div or the controller defined div without defining them with and class or id JUST using the $scope.
<div ng-controller="gridController">
<div></div> // < -- I want to access this element
</div>
To be a bit more specific does angular saves and gives access to the element DOM info which the ng-controller was called ?
A controller has no concept of the DOM, and it should stay that way or you run the very likely risk of writing untestable code. This is a part of the separation of concerns in the angular framework. A controller can be bound to multiple different elements or even to the controller function of different directives and there would be know way to tell them apart.
If you are attempting to do anything to the DOM you should be using a directive.
Given more information about what you want to accomplish with the element in question more guidance to reach your goal could be given.
You can access the element the controller is defined on by injecting the angular variable $element into your controller
angular.module('myApp')
.controller('SomeCtrl',['$scope', '$element', function($scope, $element) {
$scope.buttonClick = function () {
$element.find('div').css('background-color', '#f00');
}
}]);
If jQuery is included in your project, $element will be a jQuery object of the element your controller is defined on (the outer div in your example). You can use standard jQuery directives to access its sub elements. Accessing an unnamed child div would require you to know its position, or use some other criteria to target it.
If jQuery is not installed, angular includes jqlite which is a subset of jQuery allowing you to use most jQuery selectors to target elements, but lacking most of the other manipulation features.
However, it is generally considered bad practice to access, and especially manipulate the DOM from within a controller. Because of the way jQuery binds variables to html, if you make changes to the DOM from certain functions, angular may not pick these changes up and overwrite them when it next does a binding cycle. To avoid this, you should do most of your DOM manipulation from inside an angular directive. Sometimes however, its just easier to do it from the controller... If you are going to do this, you should learn about $apply and $digest.

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

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

Resources