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.
Related
Any thoughts on why this directive is triggering an infinite digest error?
http://jsfiddle.net/smithkl42/cwrgLd0L/13/
var App = angular.module('prettifyTest', []);
App.controller('myCtrl', function ($scope) {
$scope.message = 'Hello, world!';
})
App.directive('prettify', ['$compile', function ($compile) {
var template;
return {
restrict: 'E',
link: function (scope, element, attrs) {
if (!template) {
template = element.html();
}
scope.$watch(function () {
var compiled = $compile(template)(scope);
element.html('');
element.append(compiled);
var html = element.html();
var prettified = prettyPrintOne(html);
element.html(prettified);
});
}
};
}]);
It seems to be the very first line in the scope.$watch() function that's triggering the model update, as when I remove that line, it doesn't trigger the error.
var compiled = $compile(template)(scope);
I'm a little confused as to why that line is triggering another $digest - it doesn't seem to be updating anything directly in the scope.
Is there a better way to accomplish what I'm trying to do, e.g., some other way to check to see if the key values in the scope have actually changed so I can recompile the template? (And is there a better way of grabbing the template?)
When you use scope.$watch() with just a function and no watch expression, it registers a watcher that gets triggered on every digest cycle. Since you're calling $compile within that watcher, that's effectively triggering another digest cycle each time since it needs to process the watchers created by your template. This effectively creates your infinite digest cycle.
To use the same code, you should probably just be compiling once in your postLink function, but I don't think you even need to do that - you should be able just use the template property. Then your $watch() statement should include an expression targeting the property you want to watch for changes - in this case, just 'message', and update the HTML accordingly.
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)
});
}
};
});
I'm facing a situation in Angular where I sense I'm doing something wrong but I can't find the Angular way to solve it.
I'm developing a mobile app. I would like to create a directive let's call it cc-tap-highlight that would be used in conjunction with ng-click. Meaning that I could use it like this:
<a ng-click="doSomething()" cc-tap-highlight>Click me</a>
What this would do is to add a class to the clicked element and remove it after some seconds.
Now one could say, let's just manually bind to the elements click event in the directive. That would work for a desktop app. However, on mobile devices Angular does a lot of magic for us to have fast tap/clicks:
https://github.com/angular/angular.js/blob/master/src/ngMobile/directive/ngClick.js
For sure, I don't want to reimplement all of it's magic!
So, currently, instead of having my cc-tap-highlight directive I use this rather hackish approach:
In the view
<a ng-click="doSomething($event)" cc-tap-highlight>Click me</a>
In the controller:
$scope.doSomething = function($event){
//do your things with $event.currentTarget
}
There are two major problems with this approach:
the controller should not manipulate the DOM
We need to repeat the patter over and over through our entire code base violating DRY
However, I can't for the life of me, figure out how to write a directive that hooks into the ng-click handler and does it's things.
You can try to make your directive generate a ng-click directive with wrapper function.
Here's a quick example. It's by far not thoroughly tested but I think the principle is sound. What you want is your custom code to run before/after the click event regardless of how that's triggered(tap,click, whatever).
This does have the drawback that it creates a new scope so interaction with other directives that may need isolate scope was not tested.
DIRECTIVE
app.directive('myClick', ['$parse','$compile', function($parse, $compile) {
return {
restrict: 'A',
compile : function(tElement, tAttrs, transclude) {
//you can call the wrapper function whatever you want.
//_myClick might be more appropriate to indicate it's not really public
tElement.attr('ng-click', 'myClick($event)');
tElement.removeAttr('my-click');
var fn = $parse(tAttrs['myClick']);
return {
pre : function(scope, iElement, iAttrs, controller) {
console.log(scope, controller);
scope.myClick = function(event) {
console.log('myClick.before');
fn(scope, {$event:event});
console.log('myClick.after');
};
$compile(iElement)(scope);
},
post : function postLink(scope, iElement, iAttrs, controller) {
}
};
},
scope : true
};
}]);
CONTROLLER
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.test = function($event) {
console.log('test', $event);
};
//this is to show that even if you have a function with the same name,
//the wrapper function is still the one bound thanks to the new scope
$scope.myClick = function() {
console.log('dummy my click');
};
});
HTML
<button ng-click="test($event)">NG-CLICK</button>
<button my-click="test($event)">MY-CLICK</button>
<button ng-click="myClick($event)">MY-CLICK-DUPLICATE-FN</button>
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. :)