I'm trying to learn AngularJS and I'm trying to dynamically compile some DOM elements...
I've tried the demo:
try {
var templateHTML = angular.element('<p>{{total}}</p>'),
scope = ....;
var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
//attach the clone to DOM document at the right place
});
//now we have reference to the cloned DOM via `clone`
} catch (ex) {
alert(ex.message);
}
but all I get back is a "$compile is not defined"
HELP!
A sample code for using $compile in a directive. Basically go ahead & first append the element to DOM (might want to keep it invisible), then run the compile by using a finder.. as mentioned by rtcherry, $compile should be injected.
//
componentModule.directive('getCompilerWk', function($compile) {
return {
restrict: 'A',
link: function(scope, elm, attr) {
elm.click(function(){
$(body).append(templateHTML);
$compile($(body).find('p'))(scope);
})
}
};
});
Where are you calling this code from? Is it safe to assume it is outside of the Angular framework by your use of angular.element(...)?
If so, you can use this:
// Split across two lines for readability...
angular.element(<something within Angular's scope>)
.injector().get('$compile')(...)
If not, you may simply need to inject $compile into the controller/directive/service.
Related
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'm integrating Angular into a web application and I noticed that the wrapper is messing with some of the CSS on the page. After researching into directives, I saw that custom directives can have a property called 'replace' to be set to true so that the templateUrl directly replaces instead of being wrapped in the directive's tags.
Is there a way to do the same with ng-view, or more generally any Angular directive? Thanks for the help!
I think your best bet is to decorate the original ng-view directive and add the replace:true switch to it. Note, replace is going to be - hah - replaced in the next major version of angularjs, but for now, this will work:
app.config(function($provide) {
$provide.decorator('ngViewDirective', function($delegate) {
var directive = $delegate[0];
directive.replace = true;
return $delegate;
});
});
And of course, the jsFiddle: Here
Is there a way to do the same with ng-view
I can't think of a way of doing that without editing the source of Angular or something. replace is for custom directives, not (necessarily) the built in ones.
or more generally any Angular directive? Thanks for the help!
Same thing. But you wouldn't want to do it with any directive, seeing how you could have multiple directives in a single element. I suppose you may want to do it with all template'd directives (since I think you can only have one per element), but, again, without editing the source of the directive I can't think of a way to do that.
Also, note that Angular has stated that replace "will be removed in next major release", so it's probably not best to rely on it.
You can, however, if you wish, create you own ngInclude/ngView-esque directive that renders templates without a wrapping tag.
A naive and probably problematic one (or at least inefficient) might look like:
app.directive('myRender', function ($compile, $http) {
return {
restrict: 'E',
link: function (scope, element, attrs) {
var newScope;
var newElement;
attrs.$observe('url', function (value) {
if (value) {
$http.get(value).then(function (result) {
if (newElement !== undefined) {
newElement.remove();
}
newElement = angular.element(result.data);
if (newScope !== undefined) {
newScope.$destroy();
}
newScope = scope.$new();
$compile(newElement)(newScope);
element.parent().append(newElement);
});
}
})
}
}
});
http://plnkr.co/edit/Vm96f2rQsT2PX1C3rgK8?p=preview
My first advice would be to adapt your css to angular if you want to use angular.
My other advice would to do your entire application in angular, and not add it after the fact.
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
angular.element('<my-directive></my-directive>')
Why I can not just say the name of my directive ?
Seems so strange to write HTML line inside js...
Any better way for creating instance of directive in memory?
A common pattern for writing directives is like this.
angular.module('myApp.directives', ['myApp.services'])
/*
toggleclass
A generic directive for toggling a class directly on an element
or by specifying a toggle-target option
eg. toggleclass="{class: 'open', target='.element'}"
*/
.directive('toggleclass', function(){
return {
restrict: 'A',
link: function(scope, element, attrs){
element.on('click', function(){
var ops = scope.$eval(attrs.toggleclass);
// If a toggle-target is set
if(ops.target){
element = angular.element(document.querySelector(ops.target));
}
// Toggle the class
element.toggleClass(ops.class);
});
}
}
});
});
angular.element('<my-directive></my-directive>')
AFAIK this piece of code doesnt really create a directive,it just create an element tagged "my-directive".
You dont need the closing tag by the way. angular.element('<my-directive>') is valid.
If you want to get verbose : angular.element(document.createElement('my-directive')) .
It is some kind of jQuery light.
to instanciate a directive you need to compile some DOM and pass a scope.
var e = angular.element('<my-directive></my-directive>');
$compile(e)($scope);
I really love how the new ng-click directive in Angular now automatically includes functionality for touch events. However, I am wondering if it is possible to access that touch-event service from my custom directive? I have lots of directives that require that I bind a click event to the given element, but I'm simply doing that using the typical jquery syntax (ex: element.on('click', function(){ ... })). Is there a way that I can bind an ng-click event to an element within a directive? Without having to manually put a ng-click tag on my element in the HTML of my view...?
I want to be able to harness the power of both click and touch events. I could obviously import a library (such as HammerJS or QuoJS) but I would prefer not to have to do that, especially since Angular is already doing it.
I can access the $swipe service and bind different elements to that, but is there a similar service for ngTouch?
For reference, this is an example of when I would want to do this:
mod.directive('datepicker', ['$timeout', function($timeout){
return {
link: function(scope, elem, attrs){
var picker = new DatePicker();
elem.on('click', function(e){
picker.show();
});
// I would rather do something like:
// elem.on('ngTouch', function(){ ... });
//
// or even:
// $ngTouch.bind(elem, {'click': ..., 'touch': ...});
}
}
}]);
UPDATE: As noted by below, the source code for the ng-click directive is here. Can anyone see a way to harness that code and turn it into a "bindable" service?
I don't think that's quite the right approach. I'd approach this by using a template within your directive and then using ngTouch within that.
mod.directive('datepicker', ['$timeout', function ($timeout) {
return {
template: '<div ng-touch="doSomethingUseful()"></div>',
link: function (scope, elem, attrs) {
var picker = new DatePicker();
scope.doSomethingUseful = function () {
// Your code.
}
}
}
}]);
UPDATE
Full example with additional attributes on the directive element:
http://codepen.io/ed_conolly/pen/qJDcr