I have a directive which contains an element inside it with width: 100%. However, the directive must know the clientWidth of that element -- that is, in pixels rather than percent. The element is buried deep in the directive's DOM.
The containing controller may have multiple instances of this directive, so doing document.getElementsByClassName('myclass')[0].clientWidth won't work. Selecting the directive's element works, but the code for that looks similar to element.children().children().children()[32].clientWidth, which doesn't look very stable. What is the right, Angular way of doing this?
Whenever you need to do any DOM manipulation in Angular, directives are the way to go. The "Angular" approach would be to use your directive's link function.
DEMO: http://plnkr.co/edit/83aZUmsRzuTUFotxUYSB?p=preview
Template Use Case:
<div element-width></div>
Directive Example:
angular.module('myApp').directive('elementWidth',function(){
return {
restrict: "A",
link:function($scope, element){
// element param being passed in refers
// to the element the directive is bound to, in this case, the DIV
// do something with the element width here
}
}
});
Hope this helps and let me know if you have any questions.
I figured it out. Although single elements do not have .getElementsByClassName, you can still use .querySelector. So my finished code was:
element[0].querySelector('.myclass').clientWidth
Related
I have a directive where I want to clone the element that the directive applies to. In one particular use of the directive, the element has a button with a click handler bound to it. I want my cloned element to also have this click handler. However, the conventional methods I know for cloning nodes aren't working for me. An example of what I've tried so far follows. Keep in mind this example is simplified for demonstration and the ultimate purpose of my directive is not just to clone the element.
module.exports = function($window, $injector){
return {
restrict: 'A',
link: function(scope, element, attrs){
// Neither of the following methods actually copy the click handler over to the clone.
var myFirstClone = element[0].cloneNode(true);
var mySecondClone = angular.element(container).clone(true)[0];
element[0].after(myFirstCone); // doesn't have the click handler
}
}
}
Is there another method that I should be using? Thanks!
Instead of cloning the element, you should use ng-if or ng-show in your top level controller. Have a property that checks if you are scrolled past a certain point. When the property evaluates to true, the element will show up. Also, you can use css to make transitions smoother (e.g. ease in/out), I wouldn't rely on javascript for visuals behaviors like that.
I would like to add a <hr> element between each clone of my transclude function except for the last one clone.
The parent directive have a custom HTML template.
In this template, I call a Attribute directive to transclude the elements.
Sample:
function directiveTransclude() {
return {
link: function (scope, element, attr, ctrl, transclude) {
transclude(function (clone, scope) {
element.append(clone);
});
}
};
}
This directive help me to manually handle the transclude.
I just have to figure out now how to conditionnally add the <hr>.
I assume to do that I need to add for example element.append('<hr>'); after the first append.
So how could I know how many elements the transclude have to append ?
Is there a $last value or something to tell me that this is the last loop ?
Thanks for the help !
The controller has been instantiated by the time the link function runs, so I would just keep track of it in a controller attribute.
I'm assuming in the below example that you're passing this directive a collection to iterate over, or at least a count of number of elements you want. I'll also assume that argument is named collection, and that you're using bindToController so that this directive binding is accessible on the controller by this point. Finally, I'm assuming you've also attached an attribute to your controller named iterationCount.
function directiveTransclude() {
return {
link: function (scope, element, attr, ctrl, transclude) {
ctrl.iterationCount++;
transclude(function (clone, scope) {
element.append(clone);
if(ctrl.iterationCount < (ctrl.collection.length - 1)) {
element.append('hr');
}
});
}
};
}
Although, my suggestion would be to do this with css, if <hr> isn't some sort of strange requirement. Using a border and the last-child selector would be MUCH simpler.
Just in case you wonder:
It's late but I just find the solution by using an alternative (with CSS).
As mentioned by #Aaron Pool, I can use last-child selector to achieve that.
the last-of-type selector is finicky, and the last-child selector is actually applied to the child
It's better (faster than DOM injection and safer visually speaking, because <hr> could easily get edited by CSS rules).
I already tried that nevertheless it didn't works as expected.
Despite, today I figure out that my LESS code was that:
&:last-child
Instead of
:last-child
So I was focusing the wrong DOM element, sadly.
I'm happy even if now I feel so dumb.
Greetings and happy new year !
I've seen a javascript solution that goes a little something like this:
var select = document.getElementById('selectId')
select.click();
Is there an AngularJS approach/best practice to the same thing? (Off the top of my head, you'd wrap the above code in an ng-click)
Yes there is. Here's the angular equivalent of what you have in JavaScript
angular.element('#selectId').trigger('click');
Working example
Any DOM manipulation in angular should occur inside of a directive.
View
<div id="selectId" clickMe>content</div>
Inside of a directive the link function triggers after the view is compiled. The second parameter in the link function is the element which the directive is placed on, this gives performance benefits since there is no need to traverse the dom. It is a JQlite element which you can directly call methods on.
Directive
app.directive('click-me', function(){
return{
link(scope, el, attr){
$(el).trigger('click');
}
}
});
so here is the desire: I need to add a directive to any html element and pass it an id. This id is then used to populate a method that submits a form that has that id on the page.
Some devs have used a button some have used an anchor tag so I do not want the type of element to be part of the method. It is intended to be highly re-usable.
I already have it working like this:
angular.module("App").directive("submitform", submitFormDirective);
function submitFormDirective(){
return {
restrict: 'A',
scope: true,
link: function($scope, $element, $attrs){
var selector = $attrs.submitform;
$element.on('click', function(){
angular.element(selector).submit();
});
}
}
}
But this uses jquery for the "on" method. I want a fully angular method. Or one that does not "fight" with the framework or use jquery.
The way I am doing it now I only need to apply this attribute to the element. submit-form="#id-of-form" And I would like to keep that functionality becasue it is clean and concise. So I need to be able to add the ng-click to the element dynamically without using the limited template function which would limit the element being effected, inside the submitFormDirective.
The problem is that the $attrs.$set('ngClick', 'submitForm()') runs after the digest and $compile does not seem to work for me. I need to be able to add the click and run the compile on only this element or use a different phase that will happen before the app compile will run. But I have tried using compile to att the attr in conjunction with link adding the method to the scope to no avail. Seems strange that you would not be able to add a event from a directive. I must be missing something. Can someone help?
Background:
I am using a custom CMS where I have limited access to the code base. So, in a few cases I plan to make some DOM manipulations using JavaScript.
Problem:
I have a container directive and the container has plain old HTML items, but am not able to mark the items as being directives from the server side. Also, the plain old HTML items contain sub-content that are directives.
Example:
Here is the before:
DIV[container-directive]
DIV.some-item-in-html
DIV[some-directive-in-the-content]
DIV.some-item-in-html
DIV[some-directive-in-the-content]
...
Here is what the DOM should look like afterwards:
DIV[container-directive]
DIV[container-item] <-- This is what needs to be inserted
DIV.some-item-in-html
DIV[some-directive-in-the-content]
DIV[container-item] <-- This is what needs to be inserted
DIV.some-item-in-html
DIV[some-directive-in-the-content]
...
Question:
Does anyone have suggestions on the best approach to injecting DOM element that are directives in-between a nesting of directives using JavaScript?
Some thoughts:
I think manipulate the DOM in advance of the compilation by angular, but I wonder if there is a way to do this within Angular's framework.
Another option is from the container directive's post-linking function, I could wrap the HTML items in directive elements called "container-item" and then $compile the items manually. So, I tried this but I get an error related to the items already having directives inside with transcluded content. Something about the "ngTransclude" being unexpected. I think this is related to the inner directives already having been processed.
I would go with your first option and manipulate the DOM ahead of angular compilation.
You can do this within a directive that contains the elements that you want to manipulate.
For example:
app.directive('body', function() {
return {
restrict: 'E',
compile: function(element, attr) {
// find the inner element and wrap it
$('.some-item-in-html', element).wrap('<div class="container"></div>');
}
}
});
Parent directives are always compiled before child directives, so you can change the DOM of the children within the compile property, and not have to worry about recompiling or re-linking directives.
[EDIT]
Thanks to Biagio for the following edit.
This method shouldn't be used with a directive with a template because the element would be assigned to the template and not the child elements.
Another alternative is doing the DOM manipulation in a function that runs at the start of the angular lifecycle.
For example:
app.run(function(){
// find the inner element and wrap it
$('.some-item-in-html').wrap('<div class="container"></div>');
});