I am trying to create a "sticky table header" component for which I need to copy parts of the transcluded content of my directive.
Depending on how I transclude the content, it works only partially at best: with $compile, expressions are updated when the underlying data changes, but ng-repeat does not seem to work at all. It does not even render the first time, let alone update later. Simply appending the partial content I found does not seem to work at all: element.append($(transcludedEl).find('.wrapper'));
To illustrate my point, I have created a plunkr using three versions of the same code: http://plnkr.co/edit/xkAkzl8ID3m5Ras3Ww31
The first is super-simple direct ng-repeat, which only serves to show what should happen.
The second uses a directive that transcludes its full content, which works but is not what I need.
The third (reproduced below) uses a directive to try and include only part of its content, which is what I need, but which does not work.
The interesting bit is this:
app.directive('stickyPartial', ['$compile', '$timeout', function($compile, $timeout) {
return {
restrict: 'E',
transclude: true,
template: '<div></div>',
link: function(scope, element, attrs, controller, transclude) {
transclude(scope, function(transcludedEl) {
// this is what i want to achieve - not working
// element.append($(transcludedEl).find('.wrapper'));
// neither is this, though it does support expressions
$compile($(transcludedEl).find('.wrapper'))(scope, function(clone) {
element.append(clone);
});
});
}
};
}]);
So far, I have tried several combinations of $compile, .clone() and .html(), but to no avail. I can neither get a working partial DOM tree from the compiled template, nor a useful partial HTML source with ng-repeat intact that I can then compile manually.
As a last resort, I might try copying the DOM after angular is done (which seemed to work, previously) and then manually repeat this process every time the relevant model data changes. If there is another way, thought, I'd very much like to avoid this.
Using https://stackoverflow.com/a/24729270/2029017 I found a solution with compile that does what I want: http://plnkr.co/edit/V4yUbiAD9EAaaJXihziv
Related
Note: I am modifying this question to improve rating.
I developed the following directive with isolated scope:
app.directive('outerIsolated', function () {
return {
restrict: 'A',
scope: {
theData:'=',
...
},
replace: true,
templateUrl: './photo-list-template.html',
link: function ($scope, elm, attrs) {
...
...
...
}
};
});
And also, I developed the following inner directive with inherited scope.
app.directive('innerInherited', ['$compile', '$timeout', '$parse', function ($compile, $timeout, $parse) {
return {
scope: false;
link: function (scope, el, attrs, ngModel) {
...
...
... }
};
}]);
The Problem:
If I use the directive outerIsolated as a parent directive for the inner directive innerInherited, then all references to inherited scope variables won't work.
The innerInherited directive works just fine on its own and has been used extensively. Now, I can't change it to isolated scope. This directive is actually called check-if-required and will loop across all child input fields to find out if anyone is required field, and make it required.
Just few days ago, I learned about directive with isolated scope which can be used to develop reusable components. I liked the idea, and I developed one call upload-photo-list which I referred to it here as outerIsolated.
Is there anyway I can solve this problem easily?
One direct way, is reverse the nesting of the directive. So, I tried to use directive with inherited scope in the outer level instead, but, the problem now is that the link function of the outer directive didn't see the elements of the inner directive after being replaced by the template. I even used this code to try to wait until the document is ready this this:
angular.element(document).ready(function (){...}
... but still, the outer directive cannot reach the HTML Elements generated by the inner directive.
Appreciate your help to find a solution.
Thank you.
Old Question:
Note: this part is obsolete. I kept it here for tracking purposes only.
I am building a simple example using ng-signature-pad and signature-pad plugins.
Click here to see the sample HTML file as per the download
I noticed that the following script tag works only if I place them before the </body> tag (same as the provided sample source code in the link above):
<script src="js/app.js"></script>
If I place the above script tag in the <head> tag it is not affected.
Could someone explain to me why?
I would need to look at the project you are referencing in more detail, but I would imagine that the library and the app.js that you are using are referencing elements on the page.
If you have the scripts in the HEAD tag, those elements are not there. By putting them at the bottom of the BODY element, you are ensuring that the elements are in fact in the browser.
The quickest way to solve my problem is as follows...
I had to use the directive with the inherited scope check-if-required to be on the outer level, and the directive with the isolated scope upload-photo-list in the inner level.
However, I had to make two modifications for the directive check-if-required:
Add $timeout in the link function to ensure the inner directive has finished rendering its HTML before looping through all the input elements as follows:
code:
var children;
$timeout(function() {
children = $(":input", el);
el.removeAttr('check-if-required');
angular.forEach(children, function(value, key) {
...
...
});
})
Must compile the element with respect to the scope of the element:
code:
angular.forEach(children, function(value, key) {
if(typeof (value.id) != 'undefined') {
if (scope.isFieldRequired(value.id)) {
angular.element(value).attr('required', true);
$compile(value)(angular.element(value).scope());
}
})
So far, this solution works well for me.
Any feedback to improve is welcomed.
Tarek
I have a directive that clones its element, passes the clone through $compile(myClone)(scope); and appends it to the DOM.
If the original element has transcluded content, ex: This is some content {{withVariable}}
How can I clone it with the angularjs expressions uninterpolated, so that the cloned element has the expressions (and thus the same dynamic content as the original), rather than the values as resolved at the time of cloning?
If I use ng-bind directive, it work as desired.
ex. This is some content <span ng-bind="withVariable"></span>
Ok, I found a solution using transclude: true on my directive.
And then I have in the link function:
link: function (scope, element, attrs , uiScrollpoint, transclude ){
transclude(scope, function(clone, scope){
// stash the un-interpolated innerHTML
element[0].srcHTML = clone[0].innerHTML;
element.append($compile(clone)(scope));
});
}
When I clone the element, I retrieve the srcHTML before compiling:
var myClone = element.clone();
if(element[0].srcHTML){
myClone[0].innerHTML = element[0].srcHTML;
}
$compile(myClone)(scope);
It seems to work, but I do see the limitation that if the original element's source is modified on the fly by JS DOM-manipulation functions that srcHTML wouldn't stay in sync. I'm thinking that this would be a pretty rare case though.
Maybe there is a better way to do this? If it's possible to get the uninterpolated HTML at clone time rather than only at transclusion time, that would really be the best.
The question is how do I clean destroy elements and scopes in AngularJS.
I've got a binary tree structure, which is implemented by recursively using the same directive.
I would like to change the binary tree structure and rebuild the tree with directives. This works fine, but it seems the old elements and scopes are not removed or destroyed properly.
Unfortunately I haven't found a good documentation on the topic of cleaning and destroying of elements. When should I actually use scope.$destroy(). (get element scope children in link does not work) Could you give me a link to a documentation?
The following code should give you an impression of my directive.
.directive('mydirective', function($compile) {
return {
priority: 1000,
restrict: 'E',
scope: {
node: '='
},
controller: 'mydirectiveController',
link: function(scope, element, attrs) {
function runLink() {
// Node
if ...
var pane1 = angular.element('<mydirective node="node.left" />');
element.children().remove(); // is not sufficient
element.append(pane1);
element.append(handler);
element.append(pane2);
$compile(element.contents())(scope);
// Leaf
else ...
element.children().remove(); // is not sufficient
$compile(widgets)(scope);
element.replaceWith(widgets);
}
runLink();
scope.$watch('', function(newValue, oldValue) {
if ...
runLink();
});
}
};
}
Couple of things... one is, because your scope: is an object in the options to the directive, you have isolate scope. So it's not the same scope as the parent scope.
Also, I believe there is something in the angular directive documentation about certain things not working when the directive can contain instances of itself. Read through all the links around directives and you should find it easily enough. I just ran into it today.
You generally only need to call $destroy() if you created a new scope as a part of setting up the directive. There are examples of this here on stack overflow.
I want to learn more about compile function and transclusion and found this Plnkr. This code contains a advanced tab directive. Based on this directive I started to create my own to see what happens and to see the values of the variables.
For my test I was playing with my 'button-bar'. When finished I had created this code:
testapp.directive('buttonBar', function($compile) {
return {
restrict: 'EA',
compile: function(cElement){
var title,
template;
template = "<div class='well'><div class='button-bar-title'></div></div>";
title = cElement.find(".title");
console.log("title:", title.html());
return function(scope, element, attributes){
var well = angular.element(template),
titleDiv = well.find(".button-bar-title");
titleDiv.append(title.html());
element.empty();
element.append(well);
$compile(titleDiv)(scope);
}
}
};
});
Nothing fancy. But when I looked at the code, I thought, why use a compile function? I think I can refactor this using a link function, so I did:
testapp.directive('buttonBar', function($compile) {
return {
restrict: 'EA',
link: function(scope, element, attributes){
var title,
template,
well,
titleDiv;
template = "<div class='well'><div class='button-bar-title'></div></div>";
title = element.find(".title");
console.log("title:", title.html());
well = angular.element(template),
titleDiv = well.find(".button-bar-title");
titleDiv.append(title.html());
element.empty();
element.append(well);
$compile(titleDiv)(scope);
}
};
});
And this gave me the exact same result.
At this point I am confused, why should the originally author of the plunker use a compile function instead of a link function? Is there a good reason for? Or are there guidelines on when to use a compile or a link function?
Edit:
For both directives I've used the same HTML (primary1Label is defined on the controller):
<button-bar>
<div class="title">De titel is {{primary1Label}}</div>
</button-bar>
In the example you provided compile is the correct method, as it executes before the template is cloned and therefore performs less manipulation on the DOM.
The most fundamental difference between the two is that compile runs once for all directives on the DOM, whereas link runs for each individual element.
As a result it's recommended that if you want to perform a template DOM manipulation you should do so within the compile function.
If you want to listen to scope variables individually and perform instance DOM manipulation (i.e. on this element only) then the link function is what you want.
There's another answer here What is the difference between compile and link function in angularjs that explains it more thoroughly.
I'm trying to get a custom directive to work inside of ngRepeat, but can't get the obvious to work. In this case I don't 'believe' I want to isolate scope. I suspect this is simply a matter of framework ignorance, but can't seem to figure it out. I have a plunk here to show: http://plnkr.co/edit/LNGJHtbh7Ay0CYzebcwr
The link function runs only once for each instance of the sel directive, so it renders the arr.name value one time. In order to make it aware of future changes, you can use a $watch:
link: function(scope, elm, attr){
scope.$watch('arr.name', function() {
elm.text(scope.arr.name)
});
}
Plunker here.
You can find more information on that in the $rootScope documentation.