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.
Related
I am still confuse where to use link and compile function.
Thanks in advance!
The link function is used in a directive.
The params of the link function reference the element the directive is attached to.
link: function(scope, element, attr)
Element here is the HTML element. and attr refers to the attributes of the HTML element
So you could do element.text('fred');
To set the text of the element to 'fred' and so on.
$compile I am not so familiar
From this SO Answer
compile function - use for template DOM manipulation (i.e.,
manipulation of tElement = template element), hence manipulations that
apply to all DOM clones of the template associated with the directive.
link function - use for registering DOM listeners (i.e., $watch
expressions on the instance scope) as well as instance DOM
manipulation (i.e., manipulation of iElement = individual instance
element).
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
This seems to me like it should be straight-forward, but I think I'm misunderstanding the order of operations here. The documentation is a bit tough for me to digest and the answers I've found here have led me closer to an answer but not quite far enough.
I'm trying to place a scope variable (containing a string) onto the DOM using a directive and I want to manipulate that text and eventually create a "Read More" text truncation function.
HTML snippet:
<read-more>{{ commentary }}</read-more>
Angular/Coffeescript:
app.directive('readMore', [ ->
restrict: 'E'
scope: false
link: (scope, element, attrs) ->
console.log(element[0])
element[0].innerText = element[0].innerText.substring(0, 125) + "..."
])
The text from that variable gets read into the DOM, and console logs the element as <read-more ng-class="ng-binding"> and the {{ commentary }} string is printed in the console between the tags, however, my function doesn't manipulate it.
I know it's the correct element (and index), but for some reason innerText and innerHTML don't affect what is on the DOM.
If I change the return line in the link function to:
element[0].innerText = "Foo"
I get nothing between the <read-more> tags in the console and, naturally, the DOM now has no content in there.
What am I missing about how link deals with this element on the DOM?
My understanding is that element you are dealing here is not the JS element you are dealing for example in a standard jQuery function. This is angularjs element, Instead of DOM manypulation, I would rather create a directive that sets the model of the "Read More" element, and also hides the entire text that needs to be displayed after click on it. But do that only via angularjs ng-model directive, not by DOM manipulation.
You doesn't need DOM for angular.
Here show you a few ways to bind content:
http://plnkr.co/edit/OSWIy0?p=preview
Use this
link: function($scope) {
$scope.otherText = 'Here more text. For example, this maybe come from http-stream.';
}
instead this:
element[0].innerText = element[0].innerText.substring(0, 125) + "..."
Good luck. :-)
Angular provides a wrapper for the element using jQuery (if available) or jQLite, so use the html or text function:
link: (scope, element, attrs) ->
commentString = element.html() ## alternatively use element.text()
Note, however, that both the text and html functions will return the unevaluated string {{ commentary }} rather than whatever string value the commentary variable holds.
To get around this, simply address commentary using the scope argument passed to the directive's link function.
link: (scope, element, attrs) ->
commentString = scope.commentary
... ## perform string manipulation here
element.text(newCommentString)
This will set the element text to whatever string you pass to it. As for updating the text: if something like "read more" is clicked, have an event handler ready (still inside of link:, like so:
element.on('click', ->
element.text(commentString) ## the full commentary string from above
)
Let's say I have a boolean format that controls how a collection of items should be displayed by my directive. Depending on the value of format, my html changes radically.
So far I'm using two separate templates, and I dynamically recompile the directive whenever format changes, by using $watch(format) in the link function of the directive.
I wonder if it would be faster to use only one template, and split all my code between a big ng-show="format" and a ng-hide="format". What's the best practice in that situation?
$compile is expensive (relatively). Your use case sounds like a candidate for ng-switch, so don't reinvent the wheel if ng-switch works for you.
However, inside your directive, you can cache the compilation of each template and then re-link them by calling the link function returned by $compile. This would be pretty much as performant as ng-switch .
In pseudo code your link function would look like:
link: function(scope, element, attrs) {
//create and cache your template link functions
var format1link = $compile(template1);
var format2link = $compile(template2);
scope.$watch('format') {
//call appropriate link function based on the value of format
var newElement = format1link(scope);
//or
var newElement = format2link(scope);
//replace element contents with newElement
});
}
Having read AngularJS : Difference between the $observe and $watch methods, and the implementation of ng-href/ng-src:
link: function(scope, element, attr) {
attr.$observe(normalized, function(value) {
if (!value)
return;
attr.$set(attrName, value);
// ...
}
}
I wonder why ng-href/ng-src are implemented by using attr.$observe instead of scope.$watch. By using scope.$watch, it looks like
link: function(scope, element, attr) {
scope.$watch(attr[normalized], function(newValue) {
// ...
})
}
then in view we could write <img ng-href="expressionFoo"> instead of <img ng-href="{{ expressionFoo }}">.
The possible reasons I could figure out are
attr.$observe makes a directive to work more like a normal DOM attribute. After link, I could affect the directive by attr.$set('ngHref', ...) in another directive.
interpolate is higher level than expression. It's easier to write plain strings and multiple expressions by using interpolate.
ng-href and ng-src both result in string attributes, so it's safe and easier to use interpolate hence attr.$observe here.
Any idea?
I think the scope isn't what you think.
Option scope in directives is false by default, which means we get parent scope, that could be anything, really.
If we want to watch for element's attribute change, we have to watch the element's attribute "ng-blah". The scope is simply not related.
If we were to use scope.watch, we probably wouldn't even have a property named after that attribute, and nothing would work.