For example, UI Bootstrap has a directive called 'typeahead' that suggests values for a field. Let's say I want to make a directive that I can use as an attribute for an element that will cause colours to be suggested for that element.
Here's an attempt that fails...
Directive:
angular.module('myApp')
.directive('suggestcolors', function () {
return {
compile: function compile(element, attrs) {
attrs.$set("typeahead", "color for color in ['red', 'blue', 'green']");
}
};
});
View:
<textarea ng-model="foo" suggestcolors></textarea>
When I inspect the textarea, the attribute has been set, but it doesn't do anything. The same thing happens if I move the change to attrs to the link function. Setting the typeahead attribute directly in the view works as expected:
<textarea ng-model="foo" typeahead="color for color in ['red', 'blue', 'green']"></textarea>
(But I want to be able to insert this attribute dynamically, for reasons of DRY. My actual use case is more complicated than this.)
A similar question was asked here (about dynamically adding ng-click behaviour in the compile step), but never directly answered.
After the compilation, AngularJS only calls $compile for all of the child elements. The element itself is not automatically recompiled, so adding a directive at this stage will not have effect. In your case, I think you can change it from a compile function to a link function (so you get a scope argument), and call $compile(element)(scope) yourself.
See this Fiddle where I have a directive that adds style="color: red", and another directive that "dynamically" adds that directive. It doesn't work, unless I call $compile afterwards.
HTH!
Related
I'm trying to build a wrapping directive for a d3 force graph.
I'm building a tool bar for the graph, and if you click something on the toolbar it should change stuff in the graph (hide/show labels, remove/add nodes)
What I would like is that the toolbar elements will each have an ng-click attribute that will reference the function that does its meaning, but it means that I have DOM manipulation code inside the controller.
So I thought to have an elem.on event handlers, but that means that my code is very dependent on my view (the code needs to know the right selector to use etc.)
I also like to use as less jquery as i can. using jqlite is ok for me, but not sure that I want to be dependent on jquery.
What is your way to do similar stuff?
Thanks,
Chaim
An appropriate place to interact with your view is in link function aka compile.post.
Link function is being triggered after controller function returns (with $scope object populated). Of course link function is part of DDO (Directive Definition Object).
.directive('sampleDrv', function () {
return {
link: function (scope, element, attrs, controller) {
}
}
}
Also I've been dealing with d3js in my angularjs apps and would recommend this approach http://www.ng-newsletter.com/posts/d3-on-angular.html
I got the following problem: My customer wants my angular directive to change the looks completely on focus. That means for me, I will need to replace the template completely when the user clicks that element. I was not able to find an answer in internet, so decided to ask.
I tried to pass the "unfocused" template in the template section of the directive, and bind to the focus event in link like this:
post: function (scope, element, attrs) {
element.bind('focus',function(event){
element.html('<span..../span>');
$compile(element)(scope);
});
}
But it produces an error (input is the "unfocused" template)
Uncaught Error: [$compile:multidir] Multiple directives
[ngModel, ngModel] asking for 'ngModel' controller on: <input....
The solution needs to be compatible with Angular 1.2 which I have to use.
ng-if will not render the element at all if the condition is false.
In your comment you have the input nested in the span, so the span is the root element. You can have both, the span and the input as top level elements in your template.
<input ng-if="focusCondition"/>
<span ng-if="!focusCondition"></span>
Then the element that is rendered should be the root element.
You can use ng-switch directive and display only those templates which have to be shown on focus.
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?
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