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.
Related
Is there any case when I don't need an ngCloak directive?
It seems that every tag that somehow uses Angular library needs to define this directive either via ng-cloak attribute or class="ng-cloak".
Is there any downside of using this directive for every possible element?
ng-cloak will cause your element not to render straight away. Often elements will look fine without ng-cloak, in which case it would be strange to opt for a blank space.
This will often be the case if you are not using the {{x}} syntax. Perhaps you could use ng-bind instead.
Lots of elements will not be in the DOM at page load. For example, the template of a directive will obviously not be available until after the digest cycle has already run. It would be strange to try to cloak something which was not visible in the first place.
Using ng-cloak more than you need to will just hurt performance.
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 =)
Adding multiple time ng-if with same variable will cost more watcher?
I mean will it be a performance issue if I use as follows?
<span ng-if="contentLoaded">{{::MY_CONTENT_1}}</span>
<span ng-if="contentLoaded">{{::MY_CONTENT_2}}</span>
Or is there any good approach to handle it?
EDIT
It seems that #Jon Quarfoth is right :
"Unless there's some kind of smart deduping going on under the covers that I don't know about, I'm pretty sure that each ng-if creates a watch.
A quick look at the source shows a $scope.$watch being created in the ngIf directive's link function:
http://github.com/angular/angular.js/blob/master/src/ng/directive/…"
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
I have a control that should display breadcrumb navigation. It needs data (route & title) to display the navigation correctly. Data is taken from scope and used inside a directive.
What causes my problems is that I use a localization directive in the control that should translate the title. And this localization directive is called even when expression in ng-show is evaluated to false. Then the translation in localization directive ends with exception because it tries to translate incorrect string (see 'localize' directive in http://jsfiddle.net/F97wn/7/).
That seems quite weird. I would expect that if something sets whether the inner content should be visible or hidden, then it is evaluated first and then the inner content..
Ok, then I found that ng-show only sets some css attribute, so it's quite useless for me.
The question is: How should I solve the problem - what to use instead of the ng-show?
An example is at http://jsfiddle.net/F97wn/7/
You could use ngSwitch instead with the on part set to "toshow()" and the inner ng-switch-when="true" part to have your custom directive inside that area. This will then not execute the custom directive if the value of toshow is not true.
If the directive is throwing an exception, more information should probably be passed to the directive, in one of the following ways, so that the directive can decide if it has the required information to do what it needs to do:
attribute data -- e.g., localize="..." show-me="..."
something defined on the scope associated with ctrl -- e.g., $scope.showMe. The directive scope will have access to this property as scope.showMe, based on the way you currently have the directive defined.
or inject a service (that has the data) into the directive -- e.g., directive('localize', function(myShowMeService) { ... }
You might also want to look into <ng-if>. The ngIf directive removes and recreates a portion of the DOM tree (HTML) conditionally based on "falsy" and "truthy" values evaluated within an expression. It might be more intuitive than <ng-show> for your needs.
However, it is currently only available in the unstable version of AngularJS. If you can use that version of AngularJS you can find more information about it here.