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. :)
Related
I am new with Angular and now I have encountered some problems...
So let's say I have a controller called ViewModelController and I use controlleras when I define the routes as following: .
And in my template I have just difened two div which seperate the container in two parts:
<div id='viewleft' class="divleft col-md-5"></div>
<div id='viewright' class="col-md-7 divright"></div>
And in ViewModelController, I have some code to render the template when the controller is loaded. The question is that the ng-click I put in all the elements just don't fire and I don't really know where is the problem.
I have tried thing like below but it just does not work.
var content1 = '<ul><li><button id ="b1" ng-click="vmCtrl.cprint($event.target)">123</button></li><ul>';
$("#viewleft").html(content1);
Can someone helps me on that? Thank you in advance, best wishes.
You have to compile this html so that angular code will work
$compile($("#viewleft").contents())(scope);
or better to use a directive that compile html when its value changes.
app.directive('compile', ['$compile', function ($compile) {
return function (scope, element, attrs) {
scope.$watch(
function (scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function (value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
}]);
in controller you can asssign html to scope variable
$scope.template= '<ul><li><button id ="b1" ng-click="vmCtrl.cprint($event.target)">123</button></li><ul>';
and on view you can add that
<div id='viewleft' class="divleft col-md-5" compile="template"></div>
You can refer this docs for $compile.
I have a video player directive that uses an ng-src in its template. How do I run directive code after the ng-src has been evaluated so the video is actually loaded?
Here is the directive code:
return {
restrict: 'A',
replace: false,
transclude: false,
scope: true,
templateUrl: "/modules/didyouknow/views/slideshow-frame.directive.client.view.html",
link: {
pre: function() {
console.log('a');
},
post: function(scope, element, attrs) {
/**
* scope.frame - frame information
*/
scope.frame = scope[attrs.slideshowFrame];
}
}
};
both link functions execute before {{expr}} has been evaluated in the template.
The whole point of post link is it that it's executed after child post-links, in reverse order as pre links. So why isn't it executing last? It executes immediately after the prelink function so why are they even separate functions?
You could have $observe inside your directive that will work same as that of the $watch but the difference is it evaluates {{}} interpolation expression.
Inside $observe you could see if the ng-src has value the only call the directive method. otherwise wait.
link: function(scope, element, attrs){
attrs.$observe('ngSrc', function(newVal, oldVal){
if(newValue){
//call desired code when `ng-src` have value
}
});
}
There is a couple of recipes to execute the code in link at the moment when directive DOM 'is there'. One is using zero-delayed timeout
$timeout(function () {
...
});
It is is generally preferable if you're after rendered DOM or interpolated values. It is not an option here, because templateUrl is used and directive template is loaded asynchronously, and the template is not available during the linking phase.
Another recipe is using scope watchers/attribute observers (one-time if you don't care about data bindings).
var unobserveNgSrc = attrs.$observe('ngSrc', function (ngSrc, oldNgSrc) {
if (!ngSrc) return;
unobserveNgSrc();
...
})
It executes immediately after the prelink function so why are they even separate functions?
This behaviour suggests what it can be used for. In parent preLink some things can be done that must precede child preLinks or the latter could benefit from, which you will rarely find useful for simple directives. And parent postLink executes last, and that's a good moment for 'ok, all of my children are already compiled and linked, let's do something at last'.
I have limited knowledge of Angular so please bear with me. I am in a situation where I can only modify one js file which is included BEFORE all of the Angular stuff. There is a directive that is causing a problem, yet I can not modify it directly. So I've tried to override it by adding the snippet below in a document ready block:
app.directive('selectionChange', function($rootScope){
return {
priority: 1,
terminal: true,
link: function(scope, el, attr) {
console.log('works');
};
};
});
I can see this directive added to the end of the invokeQueue, but it is never executed. How do I get this attached? Thanks!
UPDATE:
Sorry, let me try to clarify. Problem is, the original directive continues to fire, but the newly attached one does not (tested by using console.log and alert). The markup is something like this:
<html>
<head>
...
<script src="[the file I can modify].js"></script>
...
</head>
<body>
...
<script src="angular.js"></script>
<script src="directives.js"></script> // here is where the existing selectionChange directive is defined
...
</body>
</html>
Here's a plunker
$(function () {
var app = angular.module('app');
app.config(function ($provide) {
$provide.decorator('badDirective', function ($delegate) {
var badDirective = $delegate[0];
var link = function (scope, element) {
element.text('good');
}
var originalCompile = badDirective.compile || function () {};
badDirective.compile = function () {
originalCompile.apply(badDirective, arguments);
// compile returns link fn, directive 'link' property will be ignored anyway
return link;
}
return $delegate;
});
});
})
Doing it on 'ready' state (e.g. jQuery ready implementation) is the right thing. This way the code will be launched before the bootstrapping process (it will be queued on 'ready' via ng-app as soon as angular.js is loaded).
bad directive is just badDirective service internally which contains an array of DDO (because there can be several directives with the same name). And it can be decorated, as any other service.
Link function can be defined with either link or compile (it can return link) DDO properties. The second overrides the first, so always stick to compile when decorating directives.
In the file you can modify create a script tag with a (reference/definition) to your new directive and place that tag at the bottom of the body right after the troubled directive definition. By being the last one defined you'll ensure that is your directive the one rendered.
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 looking to compile the contents of transcluded stuff before the link function executes. Currently, if I transclude an ng-bind-safe, the contents will not be added until after my link function.
I can do a scope.$apply() in the link function (and it works) but I get console errors since the digest cycle is already in progress.
Thoughts? Thank you!
During the compilation and linking phases ($compile and $link functions), you only have access to your template in your $compile function, and access to your scope and template in the $link function. You do not have access to the rendered template yet because it hasn't happened yet. For that, you need to setup a watch expression, which you will provide call back functions for. Angular will let you know when the value that you're watching has changed, and within this call back you have access to the rendered template.
This watch expression can only be done within the $link function because that is only place in the directive that you can properly access the scope.
Here is an example:
app.directive('tmTime', function() {
return {
restrict: 'A',
template: '<div>{{time}}</div><div ng-transclude></div>',
transclude: true,
link: function (scope, element, attr) {
scope.time = 'Its hammer time!';
scope.$watch('time', function(newVal, oldVal) {
// this is your call back function
// within here, you have access to the rendered template
alert(element[0].outerHTML); // (it's hammer time! in first div, transcluded contents in second div)
});
}
};
});