I have a bunch of directives in an html page, let's say the directive is <my-directive></my-directive>. These directives are added or changed outside of AngularJS, e.g. a jQuery function will simply append a new <my-directive></my-directive> to the page.
Here's my question - after appending the new <my-directive></my-directive>, the directive doesn't actually do anything - it's just a tag without any functionality.
How can I force Angular to recognize that a new tag has been added? I've tried mucking around with scope.apply but haven't had any luck.
Thanks!
I think you're looking for angulars $compile method:
myApp.directive('addonclick', function($compile){
return {
restrict: 'A',
link: function(scope, element, attrs){
var html = attrs.addonclick; //or something else
element.on("click", function(){
$(element).append($compile(html)(scope));
})
};
};
});
EDIT:
Try getting the scope first:
var scope = angular.element("yourElement").scope();
Then get and call the compile service:
var compile = angular.element("yourElement").injector().get("$compile"); //or
var compile = angular.injector(["moduleName"]).get("$compile");
$("yourElement").append(compile("<my-directive/>")(scope));
Related
I'm trying to call an angular directive with html text I am appending in my controller like so,
var loadReviews=function(){
var theDiv=$("#rlist")
for(var i=0; i<vm.reviewlistByUpvote.length; i++){
var review=vm.reviewlistByUpvote[i];
var html='<a star-directive ng-model="review.overall" data-size="xs" data-disabled="true"> </a>';
theDiv.append(html)
};
};
And my directive looks as follows,
angular.module('App')
.directive('starDirective', function() {
return {
restrict: 'A',
// templateUrl: 'views/star.html',
link: function(scope, element, attrs, ngModel) {
$(element).rating(scope.$eval(attrs.starRating));
// get value from ng-model
ngModel.$render = function() {
$(element).rating('update', ngModel.$viewValue || '');
}
$(element).on('rating.change', function(event, value, caption) {
ngModel.$setViewValue(value);
});
}
};
});
However, the directive won't compile. If I load the star directive within my html, it works fine, but through this approach, nothing gets loaded. I've looked into $compile but it did not fix the issue, but I may have applied it incorrectly.
I would avoid adding manually the directive into the html, I would recommend let angular being in charge of adding html content.
Now there are cases where you will need to append html content (like in modals), in this case you need to compile the html using $compile service before appending the html and then assing a scope (it can be a new one or the same one)
Here is a cool example from Ben Lesh of how to do that:
http://www.benlesh.com/2013/08/angular-compile-how-it-works-how-to-use.html
I want to add the following jQuery function to an existing angularjs application:
$.fn.stars = function() {
return this.each(function(i,e){$(e).html($('<span/>').width($(e).text()*16));});
};
$('.stars').stars();
http://jsbin.com/IBIDalEn/2/edit?html,css,js,output
The html for this should be:
Rating: <span class="stars">4.3</span>
But: where do I have to put the jquery function in order to work with angularjs? And where do I have to call this $('.stars').stars();?
I know this isn't answering your question directly #Michael does a good job of that. However i think its worth noting that for something as simple as this there is no need for jquery. You could roll out your own simple directive and do it right with angular. Plus you leverage data binding to make it update itself.
Plus Michael doesn't answer the issue of where do you extend JQuery to use your custom stars() method? It shouldn't be in the directive otherwise it will be called every time a directive is added to the page. (image if it was in a ng-repeat)
.directive('stars', function () {
return {
restrict: 'EA',
template: '<span class="stars"><span ng-style="style"></span></span>{{stars}}',
scope: {
stars: '='
},
link: function ($scope, elem, attrs){
$scope.$watch('stars', set);
function set(){
$scope.style = {
width: (parseFloat($scope.stars) * 16) + '%'
};
}
}
}
});
Its quite simple you define your template, the two spans. Then you watch the $scope.stars property so you can update your rating should the value change.
see fiddle: http://jsfiddle.net/g7vqb5x9/1/
You should never manipulate the DOM inside a controller. A directive link is the correct spot for DOM manipulation.
Angular.element is already a jQuery object:
angular.module('app', [])
.directive('stars', function() {
return {
restrict: 'C',
link: function($scope, element) {
element.stars();
}
};
});
BTW: a span is an inline element and has NO height and width. you need to use a block element or override the display attribute.
Plunker
I have this code:
<body ng-controller="testController">
<div test-directive transform="transform()">
</div>
<script type="text/ng-template" id="testDirective.html">
<div>
<p>
{{transform()}}
</p>
</div>
</script>
<script>
angular.module("Test", [])
.directive("testDirective", function() {
return {
templateUrl: "testDirective.html",
scope: {
transform: "&"
},
link: function(scope) {
}
};
})
.controller("testController", function($scope) {
$scope.transform = function() {
return "<a ng-click='somethingInController()'>Do Something</a>";
};
$scope.somethingInController = function() {
alert("Good!");
};
});
</script>
</body>
So basically what I want to accomplish is to create a directive with a method that will be called from the controller. And that method will do something with the values passed (in this example it does not receives nothing, but in the real code it does).
Up to that point is working. However, the next thing I want to do is create an element that will call a method in the controller. The directive does not knows what kind of element will be (can be anything) nor what method will be. Is there any way to do it?
Fiddle Example:
http://jsfiddle.net/abrahamsustaita/C57Ft/0/ - Version 0
http://jsfiddle.net/abrahamsustaita/C57Ft/1/ - Version 1
FIDDLE EXAMPLE WORKING
http://jsfiddle.net/abrahamsustaita/C57Ft/2/ - Version 2
The version 2 is now working (I'm not sure if this is the way to go, but it works...). However, I cannot execute the method in the parent controller.
Yes. However there is a few problems with your code. I will start by answering your question.
<test-directive transform='mycustommethod'></test-directive>
// transform in the directive scope will point to mycustommethod
angular.module('app').directive('testDirective', function() {
return {
restrict: 'E',
scope: {
transform: '&'
}
}
});
The problem is that printing the html will be escaped and you will get < instead of < (etc.). You can use ng-bind-html instead but the returned html will not be bound. You will need to inject the html manually (you can use jquery for this) in your link method and use var compiled = $compile(html)(scope) to bind the result. Then call ele.after(compiled) or ele.replace(compiled) to add it to your page.
I finally get to get it working.
The solution is combined. First of all, I needed to add another directive to parse the element I wanted:
.directive("tableAppendElement", function ($compile) {
return {
restrict: "E",
replace: true,
link: function(scope, element, attrs) {
var el = angular.element("<span />");
el.append(attrs.element);
$compile(el)(scope);
element.append(el);
}
}
})
This will receive the element/text that will be appended and then will registered it to the scope.
However, the problem still exists. How to access the scope of the controller? Since my directive will be used by a lot of controllers, and will depend on the model of the controller, then I just set scope: false. And with that, every method in the controller is now accessible from the directive :D
See the fiddle working here. This also helped me because now, there is no need to pass the transform method, so the controller can be the one handling that as well.
I'm using the AngularUI bootstrap library in my project. In particular, I'm using the accordion and a custom template to display the accordion groups etc. On the template I have an ng-click event which seems to be working only when I don't have the parameter on it. Inside my directive I have a scope variable that produces a unique identifier which I have included as a parameter on the ng-click event. What am I missing to get this working? I'm using Angular 1.0.8.
thanks in advance
!-- template
<a callback="accordion_group_opened($index)" ng-click="callSubmit(currentRow)">
!-- scope variable incremented each time the directive is called
countRow = generateUnique.getNextIdStartingAtOne();
scope.currentRow = countRow;
Edit:
Added the compilation still not getting the value from the scope into my ng-click param. Any ideas on a work around?
compile:function (scope, element, attrs) {
var countRow = generateUnique.getNextIdStartingAtOne();
scope.currentRow = countRow;
return function(scope, element, attr) {
$timeout(function() {
var fn = $parse(attr["ngClick"]);
element[0].on("click", function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
})
};
}
Is there a way to call an Angular function from a JavaScript function?
function AngularCtrl($scope) {
$scope.setUserName = function(student){
$scope.user_name = 'John';
}
}
I need the following functionality in my HTML:
jQuery(document).ready(function(){
AngularCtrl.setUserName();
}
The problem here is my HTML code is present when page is loaded and hence the ng directives in the html are not compiled. So I would like to $compile(jQuery("PopupID")); when the DOM is loaded.
Is there a way to call a Angular function on document ready?
Angular has its own function to test on document ready. You could do a manual bootstrap and then set the username:
angular.element(document).ready(function () {
var $injector = angular.bootstrap(document, ['myApp']);
var $controller = $injector.get('$controller');
var AngularCtrl = $controller('AngularCtrl');
AngularCtrl.setUserName();
});
For this to work you need to remove the ng-app directive from the html.
The answer above although correct, is an anti-pattern. In most cases when you want to modify the DOM or wait for the DOM to load and then do stuff (document ready) you don't do it in the controller but in he link function.
angular.module('myModule').directive('someDirective', function() {
return {
restrict: 'E',
scope: {
something: '='
},
templateUrl: 'stuff.html',
controller: function($scope, MyService, OtherStuff) {
// stuff to be done before the DOM loads as in data computation, model initialisation...
},
link: function (scope, element, attributes)
// stuff that needs to be done when the DOM loads
// the parameter element of the link function is the directive's jqlite wraped element
// you can do stuff like element.addClass('myClass');
// WARNING: link function arguments are not dependency injections, they are just arguments and thus need to be given in a specific order: first scope, then element etc.
}
};
});
In all honesty, valid use of $document or angular.element is extremely rare (unable to use a directive instead of just a controller) and in most cases you're better of reviewing your design.
PS: I know this question is old but still had to point out some best practices. :)