I use angular js 1.0.3 and I try to test my directive.
we use jQuery that is loaded automatically by angular and is accessible as angular.element that is passed to directive.
how can I add properties to the element before directive is linked with scope???
var def = '<input data-my-directive="" />';
var scope = $rootScope.$new();
var linked = $compile(def);
// do something to add property something that jq is adding
var directive = linked(scope);
my directive is something like
return function(scope, element, attrs) {
element.jq-plugin-method();
}
and my target is element passed to directive after linkage.
thanks for help
To answer my own question. it is sufficient to add
var jqLite = angular.element;
jqLite.prototype.jq-plugin-method = function(c) {...};
before
linked = $compile(definition);
I was blind or something yesterday or maybe I was adding this line after compile and it was too late.
Related
I already know what is the purpose of each item in : compile vs link(pre/post) vs controller
So let's say I have this simple code :
HTML
<body ng-controller="mainController">
{{ message }}
<div otc-dynamic=""></div>
</body>
Controller
app.controller("mainController", function($scope) {
$scope.label = "Please click";
$scope.doSomething = function() {
$scope.message = "Clicked!";
};
});
Directive
app.directive("otcDynamic", function($compile) {
var template = "<button ng-click='doSomething()'>{{label}}</button>";
return {
compile: function(tElement, tAttributes) {
angular.element(tElement).append(template);
for (var i = 0; i < 3; i++) {
angular.element(tElement).append("<br>Repeat " + i + " of {{name}}");
}
return function postLink(scope, element, attrs) {
scope.name = "John";
}
}
}
});
So as we can see , I modify the template (at the compile function - which is where it should be actually)
Result ( plnker):
But
I didn't know that template:... can also take a function.
So I could use the template function instead (plunker) :
app.directive("otcDynamic", function() {
var template1 = "<button ng-click='doSomething()'>{{label}}</button>";
return {
template: function(element, attr) {
element.append(template1);
for (var i = 0; i < 3; i++)
element.append("<br>Repeat " + i + " of {{name}}");
},
link: function(scope, element) {
scope.name = "John";
}
}
});
Question
If so - When should I use the template function vs compile function ?
Let me try to explain what I understood so far.
Directives is a mechanism to work with DOM in Angular. It gives you leverage of playing with DOM element and it's attribute. So it also gives you callbacks to make your work easy.
template , compile and link are those examples. Since your question is specific with compile and template I would like to add about link as well.
A) Template
Like it state, it is a bunch of HTML tags or files to represent it on DOM directly as the face of your directive.
Template can be a file with specific path or inline HTML in code. Like you stated above. template can be wrap in function but the sole use of template is the final set of HTML which will be placed on DOM. Since you have the access to element and its attributes, you can perform as many DOM operation here as well.
B) Compile
Compile is a mechanism in directive which compiles the template HTML or DOM to do certain operation on it and return final set of HTML as template. Like given in Angular DOC
Compiles an HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together.
Which clearly says that, this is something on top of template. Now like I said above you can achieve similar operations in template as well but when we have methods for its sole purpose, you should use them for the sake of best practice.
You can read more here https://docs.angularjs.org/api/ng/service/$compile
C) Link
Link is used to register listeners like $watch, $apply etc to link your template with Angular scope so that it will get binded with module. When you place any directive inside controller, the flow of scope goes through the link that means the scope is directly accessible in link. Scope is sole of angular app and thus it gives you advantage of working with actual model. Link is also useful in dom manipulations and can be used to work with any DOM element using jQlite
So collecting all above in one
1. Template is the primary source of DOM or HTML to directive. it can be a file or inline HTML.
2. Compile is the wrapper to compile HTML into final template. It is used to gather all the HTML element and attribute to create template for directive.
3. Link is the listener wrapper for various scope and watchers. It binds scope of current controller with html of template and also do manipulation around it.
Hope this helps a bit to understand. Thanks
I have a simple angularjs directive that I use to show a tooltip.
<div tooltip-template="<div><h1>Yeah</h1><span>Awesome</span></div>">Click to show</div>
It works fine but now I'm trying to use it inside a timeline javascript component (visjs.org)
I can add items with html to this timeline like this
item...
item.content = "<div tooltip-template='<div><h1>Yeah</h1><span>Awesome</span></div>'>Click to show</div>";
$scope.timelineData.items.add(item);
The item is well displayed on the page BUT the code of the tooltip-template directive is never reached.
I suspect that because a third party component is rendering the item, the dom element is not read by angular.
I've tried to do a $scope.$apply(), $rootScope.$apply but the result is the same. The directive is never reached.
How can I tell angular to read my dom to parse these directives ?
Here is the directive code :
.directive("tooltipTemplate", function ($compile) {
var contentContainer;
return {
restrict: "A",
link: function (scope, element, attrs) {
var template = attrs.tooltipTemplate;
scope.hidden = true;
var tooltipElement = angular.element("<div ng-hide='hidden'>");
tooltipElement.append(template);
element.parent().append(tooltipElement);
element
.on('click', function () { scope.hidden = !scope.hidden; scope.$digest(); })
$compile(tooltipElement)(scope);
}
};
});
Edit
Added plunker : http://plnkr.co/edit/lNPday452GiZJBhMH4Kl?p=preview
I tried to do the same thing and came with a solution by manually creating scope and compile'ng the html of the directive with the scope using $compile method. Below a snippet
I did the below part inside a directive that created the timeline . Using the scope of that directive ,
var shiftScope = scope.$new(true);
shiftScope.name = 'Shift Name'
var shiftTemplate = $compile('<shift-details shift-name="name"></shift-details>')(shiftScope)[0];
I passed shiftTemplate as the content and it worked fine .
But trying to do this for >50 records created performance issues .
I'm trying to build a mechanism which will allow me to resolve directives within a linking function.
Example:
angular.module('directives', [])
.directive('myContainer', ['$compile', function ($compile) {
return {
restrict: "E",
replace: true,
link: function (scope, element, attrs) {
angular.forEach(scope.component.components, function(component){
var newScope = scope.$new()
newScope.component = component;
var elem = angular.element('<'+component.type+'>'+'</'+component.type+'>')
//Trying to compile directive to be resolved
var resolvedDirective = $compile(elem)(scope)
element.append(resolvedDirective)
})
}
});
Problem is, the "resolvedDirective" (defined by component => type) simply creates a tag containing the other directive name, which will be resolved later on.
My mechanism was simplified for the sake of the example (recursive...)
Hope I've made my question clear enough...
Thanks in advance!
Need more clarity in your Question . Please update with desired result of your directive.
You can follow this pattern. This answer has explained lot about how to add compile directives within directives
Angularjs directive add directives as attribute and bind them dynamically
In your case you want to append new directive , compile and bind it .
So try to add append your element in Compile phase and use $compile in postLink phase.
I would like to interact with a scoped inside an appended html element on the page, can someone please show me how to update that scope?
var overlay = angular.element('<div id="flyout-overlay" class="page-overlay global" ng-show="testScope"></div>');
mainContent.append(overlay);
$timeout(function(){
$scope.testScope = true; // how?
},500);
use $compile service
$compile(overlay);
here is the documentation
don't forget to add $compile dependency in the controller
doc says,
apply to your case,
'<div id="flyout-overlay" class="page-overlay global" ng-show="testScope"></div>'
1 : compile - $compile collect all the directives, for ex, it will collect ng-show directive
2: link - combine the directive with a scope.. , for ex, it will bind the ng-show="testScope" directive with the scope.
#Kalhano gave the perfect answer but since you don't know about the $compile service, here is the code for you. Just a small change....
var overlayTmpl = angular.element('<div id="flyout-overlay" class="page-overlay global" ng-show="testScope"></div>');
var overlay = $compile(overlayTmpl)($scope);
mainContent.append(overlay);
$timeout(function(){
$scope.testScope = true; // how?
},500);
in nutshell, $compile service compiles your html and links it to the scope you provided,
for better understanding read the angular documentation.
What I want to do, is to handle transclude by hand and modify the content before I insert into the DOM:
return {
restrict: 'E',
transclude: true,
template: '<HTML>',
replace: true,
link: function(scope, element, attrs, ngModelCtrl, $transclude) {
var caption = element.find('.caption');
$transclude(function(clone) {
console.log(clone);
clone.filter('li').addClass('ng-hide'); // this don't work
clone.addClass('ng-hide'); // same this one
clone.attr('ng-hide', 'true'); // same this one
$compile(clone)(scope.$new()).appendTo(caption);
caption.find('li').addClass('ng-hide'); // and this
});
}
}
In angular.js source I found this example:
var templateElement = angular.element('<p>{{total}}</p>'),
scope = ....;
var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
//attach the clone to DOM document at the right place
});
//now we have reference to the cloned DOM via `clonedElement`
but when I add clonedElement.appendTo(caption); inside link function it only add comment with ng-repeat inside.
I need this because I need to hide all elements in this case
<dropdown>
<li ng-repeat="item in items"><a>{{item.label}}</a></li>
</dropdown>
I need to modify the template before compile or DOM after ng-repeat is expanded. Before would be better because I will be able to add logic using ng-hide directive instead of ng-hide class.
I realise it's been a long time since this question was posted, but I hope you may find the following useful.
I've been quite long and heavily in this (transclusion) business, I tried a few ways to achieve what you #jcubic need and finally I came across a solution which is really robust and quite simple.
...
replace: false,
transclude: false,
compile: function( tElement, tAttributes ) {
// store your "transcluded" content of the directive in the variable
var htmlContent = tElement.html();
// then remove it
tElement.html('');
return function postLink(scope, elem, attrs) {
// then html var is available in your link!
var $html = $('<div />',{ html:htmlContent }); // for much easier manipulation (so you can use DOM functions - you can also manipulate directly on htmlContent string)
// so you can manipulate the content however you want
scope.myVariable = true;
$html.find('li').attr('ng-hide', 'myVariable'); // add native directive
$html.removeClass('inner-content').addClass('my-inner-content'); // add/remove class
$html.find('#myElement').attr('my-directive',''); // add custom directive etc. etc.
// after you finished you just need to compile your html and append your directive element - also however you want
// you also convert back $html to the string
elem.append( $compile( $html.html() )(scope) ); // append at the end of element
/* or:
elem.find('.my-insert-point').html( $compile( $html.html() )(scope) ); // append the directive in the specific point
elem.find('[my-transclude]').html( $compile( $html.html() )($parent.scope) ); // once the scope:true it will be the same as native transclusion ;-)
scope.variable = $html.html(); // or you can probably assign to variable and use in your template with bind-html-compile (https://github.com/incuna/angular-bind-html-compile) - may need $sce.trustAsHtml
*/
}
}
...
So as you can see you have full control on your "transcluded" content and you don't even need transclusion! :-)
ps. I tested it with Angular 1.4. Not sure if it works with replace:true (I wasn's bother to test it as it's minor nuisance if it doesn't). You can use pre and post link as normally you'd use within compile function and you need to inject $compile service into your directive.
jcubic. You do not have to use $compile for what you are trying to do.
You can filter the transcluded element 'clone' and add css classes to the filtered nodes , but after that you have to append the modified clone to the template (it is identified by the 'element' attribute of the link function).
element.append(clone)
I created this jsfiddle for you.
If you still have other questions , please create a jsfiddle of your case.It Will be better to make an answer Thx
If you're using angular > 1.3 and ngTransclude in template, so you need to update not the clone, but transcluded DOM, eg:
elm.find('ng-transclude')
http://jsfiddle.net/jhqkxgos/
but be sure to compile found elements if you update some you need to access from controller