I'm attempting to test an ng-if in one of my templates by compiling the view against a pre-defined scope and running $scope.$digest.
I'm finding that the compiled template is coming out the same regardless of whether my condition is truthy or falsy. I would expect the compiled html remove the ng-if dom elements when falsy.
beforeEach(module('templates'));
beforeEach(inject(function($injector, $rootScope){
$compile = $injector.get('$compile');
$templateCache = $injector.get('$templateCache');
$scope = $rootScope.$new();
template = angular.element($templateCache.get('myTemplate.tpl.html'));
}));
afterEach(function(){
$templateCache.removeAll();
});
it ('my test', function(){
$scope.myCondition = true;
$compile(template)($scope);
$scope.$digest();
expect(template.text()).toContain("my dom text");
// true and false conditions both have the same effect
});
Here's a plunkr attempting to show what's happening (not sure how to test in plunkr, so I've done it in a controller) http://plnkr.co/edit/Kjg8ZRzKtIlhwDWgB01R?p=preview
One possible problem arises when the ngIf is placed on the root element of the template.
ngIf removes the node and places a comment in it's place. Then it watches over the expression and adds/removes the actual HTML element as necessary. The problem seems to be that if it is placed on the root element of the template, then a single comment is what is left from the whole template (even if only temporarily), which gets ignored (I am not sure if this is browser-specific behaviour), resulting in an empty template.
If that is indeed the case, you could wrap your ngIfed element in a <div>:
<div><h1 ng-if="test">Hello, world !</h1></div>
See, also, this short demo.
Another possible error is ending with a blank template, because it is not present in the $templateCache. I.e. if you don't put it into the $templateCache exlicitly, then the following code will return undefined (resulting into an empty element):
$templateCache.get('myTemplate.tpl.html')
Related
I'm using $compile to compile a directive on the fly. What I would like to know is if there is a way to detect when the directive is done compiling (promise?) so that I can append it to the DOM. I want to do something like this:
function newMessage() {
var directive = $compile("<div select-contacts message=\"newShare\"></div>");
// Compile directive
directive($scope).then(function(compiled) {
// After compiling, append it somewhere in the DOM
angular.element('#new_message').html(compiled);
});
}
I've searched the documentation of $compile and I'm still not clear on how to do something like this, or if it's even possible.
Update
Here is what I have currently. This works on localhost with the timeout set to 0, however on production it only works when I introduce a delay greater than 50ms. I have 250ms to be safe, but that seems arbitrary.
function newMessage() {
angular.element('#new_message_container').html($compile("<div select-contacts message=\"newShare\"></div>")($scope));
$timeout(function() {
angular.element('#new_message').html(compiled);
content: angular.element('#new_message'),
elem: angular.element('#new_message_container'),
width: '1024px',
height: '480px'
});
});
}
Basically I have a container element that is hidden. I compile the directive and place it inside the hidden container. Then within a timeout block, I open a modal, which moves the root directive element from the hidden container to the modal element. When I run this on localhost it works. The modal opens and the directive is already compiled. On production, the directive appears not to be finished compiling before the code in the timeout block is called. The effect being that the modal opens but is empty.
To wait for the compile to finish and to be applied to the dom, a $timeout at 0 should suffice for all your needs:
function newMessage() {
var directive = $compile("<div select-contacts message=\"newShare\"></div>");
// Compile directive
$timeout(function(){
angular.element('#new_message').html(compiled);
}, 0);
}
Timeout at 0 executes your inner function at the next angular digest cycle: in other words, immediately after bindings are executed (and hence all dom elements are compiled and injected in the dom).
I have a directive with inline template containing a ng-include directive element.
When karma testing my directive and logging the result of compiled element . the tesult contains a ng comment saying the ng-include is undefined . What do I make wrong ? The directive works fine but the karma testing seems not work.
I think I know what you're asking but the question can really use some revision, and include some sample code. I'm going attempt to answer because I ran into this issue a while back and it took a lot of hair pulling to finally figure it out and the fix is really easy.
I'm have to make a few assumptions and expand on this question to clarify the scenario for future readers.
Assumption 1: your directive works in normal use outside of testing.
Assumption 2: the root element of your directive template is the one with ng-include. Basically, it looks like this:
module('myapp').directive('myDirective', function(){
return {
restrict: 'E',
replace: true,
template: '<ng-include src="myRealTemplateUrl" ></div>',
}
});
Assumption 3: When you create and run a Jasmine unit-test similar to this one, the result of elem.html() is undefined:
it('Renders something cool">', function(){
var elem = $compile('<my-directive></my-directive>')($scope);
$scope.$digest();
console.log('ELEM', elem.html());
expect(elem.html()).toContain("foo);
});
With that out of the way, this question now can be answered.
ng-include uses element transclusion. When the root node of the HTML fragment passed to $compile uses element transclusion, Angular turns that root node into a comment node. Angular then appends the rendered version of your directive as a sibling node right below that original root node. So elem is now a comment node, and therefore elem.html() is undefined.
More thorough explanations can be seen in this tutorial or here
The solution is simple though, just wrap whatever you passed to $compile inside another root element. So don't do this:
// don't do this
var elem = $compile('<my-directive></my-directive>')($scope);
do this:
// but do this
var elem = $compile('<div><my-directive></my-directive></div>')($scope);
That's all you have to do to make top-level ng-include work in a unit test.
I try do something with directive in angular, but I've some problem with $compile function in programmatically html element, call here "phe".
var phe = angular.element('<div style="background-color:orange">{{value}}</div>');
When I append "phe" after or before the directive's element, it work like a charm...
var $phe = $compile(phe)(scope);
element.after($phe);
but if I wrapped the directive element with this "phe" the $compile not work.
element.wrap($phe);
Somebody have some idea?
I have made a plunker http://plnkr.co/edit/0x2MmQ7WYmiNog0IEzTj?p=preview
it works if you change the compilation sequence... compile the element before placing it in the dom
var phe_b = angular.element('<div style="background-color:orange"> b {{value}}</div>');
var $phe_b = $compile(phe_b)(scope);
element.before($phe_b);
do same for after...
The reason it doesn't work with wrap is because wrap clones the DOM element. In other words, if you did:
var wrapper = angular.element("<div>");
element.wrap(wrapper);
console.log(wrapper[0] !== element[0].parentNode); // this would output "true"
So, the element that you compiled/linked is not the same that ends up in the DOM.
You could, of course, get the wrapping element (it's the return value of wrap) and $compile it, but you need to be careful not to re-compile/re-link certain directives that were applied on the current element (including the very same directive) and its children.
I've <start-login></start-login> and the directive
app.directive('startLogin', function(){
return {
restrict: 'E',
templateUrl: 'startlogin.html',
controller: 'LoginController'
}
});
I need to execute the directive ONLY after a check, like this:
var checkLogin = false //variable
if(checkLogin){
//do something and DON'T EXECUTE DIRECTIVE
}else{
//EXECUTE DIRECTIVE
How can I do this?
I've also jQuery in my project..
You could use the ng-if directive, like so:
<start-login ng-if="expression">
ng-if will not render the content of the tag if the expression is false.
Then in your controller you simply set expression to true when you want the tag to be visible. In your example you could use the variable checkLogin instead of expression of course.
There's also a big difference between using ng-if and ng-show. The user might not experience it, since the actual difference is that ng-show hides the content and ng-if doesn't render it at all. If we inspect the DOM between the two you will see that they look similar to the code below in the different states:
When expression is true:
<start-login ng-show="expression"></start-login>
<start-login ng-if="expression"></start-login>
When expression is false:
<start-login ng-show="expression" class="ng-hide"></start-login>
<!-- ngIf: start-login -->
This is the big difference. ng-if completely removes the element and only appends a comment to the DOM. If you use ng-show (or ng-hide), angular appends a class to the element. The class is declared as
.ng-hide { display: none; }
which only instructs the web browser to not display the element in the layout. The element is still "rendered" though, or executed might be a better word, which is the main difference here. This can have a real impact when loading the page, specifically if the content in the ng-if element loads data from the server for instance. It also means that if your directive modifies the DOM and adds sensitive information, then you should use ng-if!
I'd probably go with ng-if:
<start-login ng-if="checkLogin"> ... </start-login>
The ngIf directive removes or recreates a portion of the DOM tree based on an {expression}. If the expression assigned to ngIf evaluates to a false value then the element is removed from the DOM, otherwise a clone of the element is reinserted into the DOM.
https://docs.angularjs.org/api/ng/directive/ngIf
Of course you can use ngIf or ngShow/ngHide directives,but there is another way to do that.You can use angular.value(...).As you define a global value of that module,inject it in you directive,then before creating directive,you can check your value,then do whatever you want.
I need to conditionally apply ng-disabled to an element that is enclosed by a controller but does not exists at time of original compile. It is created later on by an AJAX callback and needs to be enabled/disabled based on certain conditions. I guess it needs a directive, but how do I go about it?
I need to use ng-disabled and no other solution as it is well handled by IE<11, which do not support pointer-events.
The real code is too complicated to be quoted here, but let me rephrase the problem.
A jQuery lib does something like:
$.get(url, function(){
$('<a class="btn"/>').appendTo(myDiv)
});
myDiv is within an angular controller. The <a/> element does not exist at time of compilation/directive linkage. Right after it gets appended, I should call some code to test and apply ng-disabled condition. Should it be a directive or a watch?
You could create a directive with an ngIf (so that it's created just when ngIf condition equals true. You can enable this condition after the response returns). Then in the link function you could:
link: function( scope, element, attrs ){
element.removeAttr("name-of-the-directive");
element.attrs("ng-disabled", "{{myExpression}}");
$compile( element)(scope);
}
The line element.removeAttr("name-of-the-directive"); is necessary to avoid infinite compiling loop.
When you're inserting code into the DOM manually, you need to compile it and link it to your scope first.
If your use-case allows it, you could try using ng-bind-html in your template and then just put the loaded code into a $scope property without needing to $compile it yourself.