I have a directive that does the following:
Adds another directive attribute to the element.
Removes its own attribute.
Calls $compile() on the element to make AngularJS re-compile the element so the new directive is attached.
This works fine, except when I also add an ng-if to the element. See this minimal example and follow the steps below to demonstrate.
https://embed.plnkr.co/ymk0RwGopGF1KvesWmvA/
Press + any number of times to add to "count".
Press 0 to reset "count".
Press + any number of times again.
I'd expect the "my-test shown" <p> tag to be deleted from the DOM once its ng-if condition is no longer true after step #2. Instead, it stays around, and you'll see an extra copy of the message after step #3.
I assume calling $compile($element)($scope); in the my-test directive link function is having some unintended consequence, but I don't understand what's going on here. Any ideas?
Thanks,
David
As far as I can understand, when you change the value of count with 0, your directive is destroyed before changing the value of count. So that unremoved directive's count value is still 1.
If you use ngShow instead of ngIf, you can solve this. Because ngShow attribute doesn't trigger $destroy event and doesn't remove element from your view. So that directive can catch new value of that count. Or you can use prelink instead of link for catching updating of value of count.
As others have answered, the short solution is to use ng-show instead of ng-if or to not use $compile like that. With that aside, you might have your good reasons why you would want to use ng-if and $compile like this.
This question interested me on the note of using $compile with an isolate scope from ng-if. I did a bit of experimenting with this fork and will try to explain what I found.
We already know ng-if creates an isolate scope, but then passing that element with ng-if on it through $compile creates another isolate scope (and would make the newly compiled ng-if be looking at variables on the first-round isolate scope - the directive's $scope value).
To re-iterate that, we're having some scopes looking like (value in [] is scope.$id):
main/outer controller has scope[2]
ng-if my-test element has ng-if looking at scope[2].count and creates scope[3]
my-test linker therefore has $scope.$id == 3;
my-test does $compile - recompiled ng-if element: creates new isolate scope[4] and is looking at scope[3].count
when scope[2].count hits 0 - scope[3] gets $destroyed (because scope[3] was created by that first ng-if which is still lingering around somewhere) ... BUT! the element is A. still there and B. its count isn't updating - WHY?
Well because the element that's still there is the one that was $compiled and has A. an ng-if looking at scope[3].count (which is now $destroyed) and B. its own new isolate scope[4] (created by re-compiling ng-if element with parent scope[3])
So ya. That is all very confusing and you might just be asking... well how do I fix this??
TL;DR;
The simplest solution:
$element.removeAttr('ng-if'); before you do $compile($element)($scope);
If you've been following along, this works because the original ng-if is still looking at scope[2].count, and the element that is present is no longer getting a second isolate scope.
I dont know how to explain properly but ng-if adds a new scope to the element, his own scope, check this so question to see more details: what is the difference between ng-if and ng-show/ng-hide. I tried with ng-show and it worked the way you want:
ng-show="count > 0"
Hope it helps =)
Related
Angular 1.4.8
Here is JSFiddle.
I have a directive used twice on one page. I only change some attributes of the directive. It appears, that I get only the last defined attributes inside searchRelations function.
Is it possible to get relations A attributes when I search in the first text input and get relations B attributes when I search in the second one?
Just add
scope: true
in your directive, otherwise, they both share the parent one.
As #ssougnez said
scope: true
The important difference between setting it to true instead to {} is
that the new scope will prototypically inherit the properties from the parent scope.
More explanation here:
What is the difference between scope:{} and scope:true inside directive?
I broke the search-bar directive into a self contained thing, that has it's own isolate scope, as well as cleans up your template. Check it out: https://jsfiddle.net/y61rca5e/5/
This totally worked!
I am developing a custom directive and now I need to handle ng-if. This is easy enough when ng-if is on the directive it self. However when ng-if is on an enclosing container element my compile function fails with run time errors.
Question is: what is the best and most efficient way to know, inside the compile function of a directive, that the directive is enclosed within a container with ng-if is false OR an expression that evaluates to false.
For example:
<div ng-if="false">
<my-directive></my-directive>
</div>
And another example:
<div ng-if="somescopeValue > 300">
<my-directive></my-directive>
</div>
Maybe the answer is using jquery to traverse the dom and find a parent with
an ng-if attribute but I was wondering if there is another more "angular" way of doing this.
Your directive shouldn't rely on knowing whether it is encapsulated in a ng-if expression. That breaks the principle of 'Separation of Concerns' and deteriorates code re-use. Consider redesigning your custom directive in such a way that it needs not be aware of its host elements, in the same fashion that all components (should) work.
If you are using same scope as parent controller or directive then you are able to access same variable in your directive(here my-directive) also. If this is not a case then you need to write code to access parent element in link function of my-directive. You will get directive element in link function as a parameter.
I'm not sure how the error happens, because when your directive is inside ng-if="false", your directive won't get called at all - all compile, link and controller function in the directive will only run as soon as your directive gets added in DOM.
Perhaps your error can be solved by initializing something in case your directive is not added.
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.
I'm trying to create a new directive that selects among several children using an ng-switch. This example is not much more than creating an ng-switch inside a directive, but eventually the directive will have more display sugar and some automatic functions so it seemed that creating a directive was the right solution.
My progress so far is in this plunker:
http://plnkr.co/edit/yeCiIOCQswYJHyTozQUZ
The $compile I'm doing seems to be evaluating the switch, and determining that the value doesn't match any of the when clauses which shouldn't be true. You can see that by inspecting the elements in the rendered picker.
I'm also concerned that calling $compile at this stage seems to have thrown away the item list, so it seems like I'm barking up the wrong tree.
How do I get the transcluded content to re-evaluate within the current state?
Update
I think I was barking up the wrong tree. Mathew's answer got me started in the right direction, so it was a big help.
As far as I can tell trying to construct a directive (ng-switch) inside a directive is a bad idea. In the previous plunker when the compile happened the template was changed permanently. That means if I changed the which parameter it wouldn't update. That's what was smelling funny to me in the first place.
Here is a revised plunker:
http://plnkr.co/edit/WUVgdXjwedxO4356321s
In this case, there's a watch on the 'which' value that refires the transclusion. That function removes the previous entry (if any) and adds the new one. There's a couple added benefits.
First I removed the 'item' directive. There's no reason for it to exist, since I'm just looking at the class. Second, I used $animator to do the list manipulation. That means you can add ng-animate to the picker and get animation effects.
I hope that helps someone else looking at this question.
There were two problems with your code:
1) Your template had which being evaluated so your on was becoming the number 1, instead of the variable you wanted which is "which":
template: '<div><div ng-switch on="which"></div></div>',
2) When you used compile, you needed to pass in the $scope like so:
$compile(sel)($scope);
Here's an updated plnkr for you: http://plnkr.co/edit/Q6ViJBvkLwQRgUKYMfS9?p=preview
What's practical difference between ngIf and ngSwitch? Both directives manipulate the DOM, but ngSwitch is more verbose. Is the typical case to just use ngIf unless you need something really big in which case use ngSwitch?
Is there a situation where ngSwitch and ngIf are not direct replacements? Or is their only practical difference the syntax?
ngIf is basically a version of ngSwitch with a single condition. It's different from ngShow in that it removes the actual DOM element rather than simply hiding it. If you're using an ngSwitch with just a singly truthy condition check, then I believe ngIf will do the same thing.
Michele Tilley's got it exactly right, I believe, particularly in pointing out the contrast with ngShow/ngHide. There's one additional difference to note: ng-If will detach and re-attach an element in-place. But ng-Switch has an outer containing element on which you declare the main directive and its condition: ng-switch="expression". The conditional content within that outer element will be append()-ed as the last child of the outer element, thus changing its position relative to any non-conditional content within the outer element.
And, see this CodePen for an interactive demo of all three, showing the differences in execution.
EDIT: This behavior changed in Angular 1.2. Elements are now left in place. The Codepen above mentions and demonstrates this, providing a link to a 1.08 Plunk that has sadly been wiped out...
One more difference would be that ngIf and ngSwitch create new scopes, while ngShow/ngHide don't.
You can think in ngIf/ngSwitch in the same way you do with if/switch when you are coding. Obviously they do almost the same thing, but there are cases that a ngIf is better and there are cases where nfSwitch is better.