Access parent element(body) in directive link function - angularjs

Given a HTML structure similar to this:
<body>
<div id="one" my-directive></div>
<div>
<div id="two" my-directive></div>
</div>
</body>
When I try to access the parent element of two It works and the log returns the parent div, but when the parent is the body, as in one case, it doesn't work and returns an empty set.
app.directive 'myDirective', ->
(scope,iElement,iAttrs) ->
console.log iElement.parent()
EDIT: My guess for this problem is that my app's body is rendered on client side and appended to the body element on module's run method. The html is inserted with $('body').html($compile(body.render())($rootScope)); and I suppose the directive is called within the $compile function before the contents are inserted in the body. Can I work around this problem?

Indeed you understood your problem correctly: $compile will trigger the template compilation and linking phases on your element, so it has no parent while doing so.
The easy way to fix that is to first append your HTML to the body, and then compile it.
var html = body.render();
$('body').html(html);
$compile(angular.element(body))($rootScope);
Or if you don't want to compile the whole body but just the new element:
var elem = $( body.render() ).appendTo($('body'));
$compile(elem)($rootScope);

Related

Variable value as directive/controller name inside template (with $compile/$interpolate)?

I am creating a directive in which template I need to use the a scope's variable value as the name of the directive (or alternatively controller) to load.
Say I have a directive widget that has a template called widget.html which looks like:
<div class="widget widget.type" {{widget.type}} ng-controller="widget.type">
<div class="navBar">
<div ng-include="widget.type + '-t.html'"></div>
<i class="fa fa-close"></i>
<hr>
</div>
<div ng-include="widget.type + '-f.html'"></div>
</div>
Now widget.type is not getting evaluated in the first line. It works fine for ng-include. Say widget.type's value is weather. The first line should then be interpolated first to look like (doesn't matter if class attribute, widget.type-attr or ng-controller is interpolated)
<div class="widget" weather>
and then compiled to include the weather directive.
How can I get widget.type interpolated in the template?
Not an option is to use ng-include to load the directive. I need to use one common template for the widget and want to add/override/extend the base directive with additonal functionality/Variables.
If this is not the way to achieve that, is there a way to extend a directive the OOP-way?
See the plunkr
You can only place interpolation expressions in text nodes and attribute values. AngularJS evaluates your template by first turning it into DOM and then invoking directive compilation, etc. If you try to place {{...}} instead of attribute name, you'll just end up with messed-up DOM.
If you really need to replace a whole directive based on $scope variable value, you'll need to create a directive for application of other directives and do some heavy lifting with $compile (you'll have to completely re-compile the template each time the value changes). I'd recommend trying to find other designs solving your situation before attempting this.
For adjusting your template based on element attributes, see this answer.

How do I bind ng-click inside a filter function

In my partial I have the following html
<div class="content" bo-html="myText | mention"></div>
which scans the text and replaces twitter #handle names with a link as follows,
text = text.replace(/\B#([\w-]+)/gm, '<a ng-controller="myCtrl" ng-click="openProfile()" class=""> #$1</a>');
Now the replace works fine but when I click the link nothing happens I tried logging inside the openProfile() function in my myCtrl but nothing happens
Any ideas?
First you need to inject in your filter $compile service, and when your text is ready you need to $compile it
text = $compile(text)
check more about compile

AngularJS : How to properly transclude child elements in custom directive?

Here's some code : link
I'm trying to create a directive that wraps its children in some boilerplate. But if the children have ng-if controlling their appearance, the "transclusion" doesn't work. Well it sort of does, but as you can see the ng-if logic is not getting passed through correctly.
I'd like to know how to fix it, but also where (if anywhere) this is described in the Angular docs.
The problem is that Angular initially replaces ngIf with a comment that it uses to track where to place the conditional code. It's easiest to see with an example.
Your HTML:
<div control-group>
<label>Test</label>
<input type="text" ng-model="editing.name" />
<span class="text-error" ng-if="editing.name.length != 3"> Name must be 3 characters </span>
</div>
Looks like this inside your transclude function's cloned variable (transclude(function (cloned) {):
<div control-group>
<label>Test</label>
<input type="text" ng-model="editing.name" class="ng-valid ng-dirty">
<!-- ngIf: editing.name.length != 3 -->
</div>
So, the element with class text-error that you're filtering on (below) isn't in cloned. Just the comment is there.
var inputsAndMessages = cloned.filter('input, button, select, .text-error');
Since you're only transcluding elements that match the above filter the ngIf comment is lost.
The solution is to filter for comments as well and add them in your append (so Angular maintains it's reference point to the ngIf). One way to do that is to replace the above with this (using the fact that an html comment is node type 8)
var messages = cloned.filter(function(){ return this.nodeType == 8; }); //comments
var inputs = cloned.filter('input, button, select')
var inputsAndMessages = inputs.add(messages);
Working plunker
You need to tell the directive where to place child elements with the ng-transclude directive: (plnkr)
template: "<div class='control-group' ng-transclude>" +
"<label class='control-label' for=''></label>" +
"<div class='controls'></div>" +
"</div>",
From the docs:
Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
I wasn't sure what exactly your intent was since you have the input and label both in the template and as children in the HTML. You might want to place your ng-transclude elsewhere.

Angularjs : dom manipulation replacing and then reattaching to another parent with map

PROBLEM
I have a dom element, which i want to detach it from its parent and append it at some other location in the dom, angularjs way.
i could easily do this in jquery with detach and appendto, but i am unable, to find the same in angularjs way.
var detached = jQuery('.toBeDetached');
jQuery('.itemMaximixed').append(detached);
Note i need to detach since the element contains a map in it.
It may not be applicable in your case, but you can use the transclude facility of an Angular directive to do this.
I don't think attaching and deattaching would work. What I can suggest is destroying the recreating the template under different node.
You can use something like ng-if in Angular 1.1.5 for this. Using multiple ng-if and associated conditions you can bind the same template at multiple location, which ever ng-if returns true that template would get loaded.
The template code can be duplicated or be out inside a ng-template script. Something like this
<div id='parent'>
<div id='child1'>
<div ng-if='condition1'><ng-include src='datatemplate' /></div>
</div>
<div id='child2'>
<div ng-if='condition2'><ng-include src='datatemplate' /></div>
</div>
</div>
<script id='datatemplate' type='text/ng-template'>
<!-- Template HTML -->
</script>

Add directive from inside another directive in angularjs

Adding directive from inside another directive, makes the browser to hang.
What im trying to do is
1) Alter an custom element directive (like <h7></h7>) inside the compile function. By doing this the browser hangs.
code:
<h7>TEST</h7>
animateAppModule.directive('h7', function($compile){
return {
restrict:"E",
compile:function(tElement, tAttrs, transclude){
tElement[0].setAttribute("ng-class", "{selected:istrue}");
return function(scope, iElement, iAttrs){
//$compile(iElement)(scope);
}
}
}
})
If i uncomment this line //$compile(iElement)(scope);, the browser hangs.
You can uncomment the above said line in this fiddle http://jsfiddle.net/NzgZz/3/ to see the browser hanging.
However the browser hanging is not happening if i have template property in the h7 directive, as shown in this fiddle. http://jsfiddle.net/KaGRt/1/.
In overall what im trying to achieve is
I want to agument the template, with new functionalities with help of induvidual directives. Somthing like decorator pattern.
I'm doing this inside the compile function of an directive which is in the directive chain so that it affects all that instances of that template.
Pseudo example of what I'm trying to achieve.
<xmastree addBaloon addSanta></xmastree>
1) Say xmastree has a template - <div class="xmastree" ng-class={blinks:isBlinking}></div>
2) Say addBaloon has a template <div class="ballon" ng-class={inflated:isinflated}></div>
Then, addBaloon compile function should augment the template from step1 to something like this
<div class="xmastree" ng-class={blinks:isBlinking}>
<div ng-repeat = "ballon in ballons">
<div class="ballon" ng-class={inflated:isinflated}></div>
</div>
</div>
3) Say addSanta has a template <div class="santa" ng-class={fat:isFat}></div>
Then, addSanta compile function should augment the template from step2 to something like this
<div class="xmastree" ng-class={blinks:isBlinking}>
<div ng-repeat = "ballon in ballons">
<div class="ballon" ng-class={inflated:isinflated}></div>
</div>
<div ng-repeat = "santa in santas">
<div class="santa" ng-class={fat:isFat}></div>
</div>
</div>
After all the compilation, if i run the template derived from step3 against a scope with suitable properties, i should be able to get the HTML.
Calling $compile on the element of the directive itself will cause the same directive to run again, which then repeats that process - forever. In the angular source code of many directives, you can see that they handle this situation like this: $compile(element.contents())(scope); using element.contents() rather than just element(). That means that everything inside of the element is compiled and the directives/data-bindings are registered and no loop is created.
If you do need to $compile the element itself, either replace the original element entirely or remove the original directive from it (remove the attribute, change node type, etc) before compiling.

Resources