Why ngIf has higher priority than {{ }} (interpolate)? - angularjs

What is the reasoning behind setting ngIf priority (600) higher than {{ }}(100)? Shouldn't it have a lower priority to allow {{ }} inside ng-if attribute value?
I would like to have a condition inside a $scope variable:
Controller:
app.controller('MainCtrl', function($scope, $http, $parse) {
$scope.hide = "check === 'hidden'";
$scope.parsecond = function (cond) {
return $parse(cond)($scope);
};
});
Template:
<body ng-controller="MainCtrl">
<div ng-if="!{{hide}}">funky ng-if div</div>
<div ng-hide="{{hide}}">ng-hide div</div>
<div ng-if="!parsecond(hide)">ng-if div</div>
<input type="input" ng-model="check" />
</body>
ng-hide works fine since it parses the contents of the hide variable and returns "check === 'hidden'" which then gets evaluated by ng-hide directive.
But ng-if tries to evaluate {{hide}} before interpolate has had a chance to parse the string hence ng-if throws an exception.
The only solution I've found is call a function that basically does the job of the interpolate directive and returns the parsed content.
Plnkr showing the issue: link
EDIT:
After reading documentation I've found better way of doing it without the need of a custom method on the $scope since angularjs has already a method that parses a variable against its current $scope ($eval).
So my solution would be:
<body ng-controller="MainCtrl">
<div ng-if="!$eval(hide)">funky ng-if div</div>
<div ng-hide="{{hide}}">ng-hide div</div>
<div ng-if="!parsecond(hide)">ng-if div</div>
<input type="input" ng-model="check" />
</body>
Updated plnkr: link
Although this still doesn't explain why ngIf has higher priority.
EDIT 2:
Just so people understand that it's not the same:
For example:
Controller:
$scope.value = "hi";
$scope.condition = "value === 'bye'";
HTML:
<div ng-hide="condition"></div> <!--This will be evaluated to true since !!"value ==='bye'" = true. -->
<div ng-hide="{{condition}}"></div> <!--This will be evaluated to false since value !== 'bye' = false -->
<div ng-if="condition"></div> <!--This will be evaluated to true since !!"value === 'bye'" = true. -->
<div ng-if="{{condition}}"></div> <!--This will give an exception since ngIf directive ran before interpolation directive in the $compile step. -->
<div ng-if="$eval(condition)"></div> <!--This will be evaluated to false since value !== 'bye' = false. -->
My conclusion is that it safer to use $parse if you want the directive to evaluate/set a watch in the string rather than in the property on the scope. Although it's true that I could use {{ }} for ng-hide/ng-show or any directive that has a lower priority than 100 but I'm guessing it's not safe since I'm depending in the compiling order and it's not 100% clear that it won't change in future patches.

ng-if expects its value to be an angular expression - under the hood it just makes use of $scope.$watch. Therefore, if you want to condition including content of ng-if on some variable defined on the scope (let say: scope.hide), you put ng-if="hide" in your mark-up. No double curly braces here.
Now back to your question: it is true that ng-if has priority of 600, but $interpolate is angular's service - not a directive. As such $interpolate does not define priority. Where did you get 100 from?
UPDATE
You can always condition including content of ng-if on some function (let say scope.conditionFn) by putting in your mark-up: ng-if="conditionFn()".
UPDATE 2
I updated your PLNKR to make it working. The inconsistencies between ng-if and ng-hide in your plunker had nothing to do with priority of interpolation taking place in $compile.
UPDATE 3
It seems that you are right that order of interpolation plays a role here, but... I really do not see any good reason to interpolate inside of angular's expression. The reason why ng-if has relatively high priority is that it removes/adds transcluded content from/to DOM, whereas ng-hide just shows/hides the transcluded content. I think it is a pure coincidence that one directive seems to work and the other not. But if you do not use unnecessary, superfluous tricks, both do work as intended, what my plunker shows.

To understand why it's doing that, observe the console of this sample here, where the custom directive, similar to ngIf, sits this time at priority 0. The directive is meant to remove the element and, without waiting, to add it back. You'll see however an error which is caused by the attempt to set the attributes back onto what remained due to transclude: elementwhich, in this case and that of the ngIf, is just a marker for where the element has been, in the form of a comment.
To avoid that from happening, ngIf terminates the process early by having a higher priority, terminal:true and by monitoring directly its expression grabbed straight from $tAttrs. Interpolation will execute, but this is done at a later stage, by calling the transclusion function the moment the ngIf expression turns true, on a clone of the original element, now under its control. The new element will show nice and dandy under the comment element.
Here is the same sample but fixed. The error condition is avoided.

Related

code in directive's controller runing although it is hidden via ng-hide

In this example:
<div ng-hide="hideMe">
<my-directive></my-directive>
</div>
I would like the code in the controller for myDirective, not to run when hideMe === true.
Is there a way to do this rather than wrapping all the code of the controller in an if (!hideMe === true) statement ?
ngHide simply hide the element, but if you want it not to be run you should use ngIf instead. According to the docs, ngIf removes or recreates a portion of the DOM tree (i.e., no element, no directive and no controller) but ngHide only change its visibility.
<div ng-if="hideMe">
<my-directive></my-directive>
</div>
You should actually go for ng-if directive as the the code inside does not get evaluated if the condition is false. You could refer to the following link for better understanding.
what is the difference between ng-if and ng-show/ng-hide

What is the diff between using {{...}} and without {{...}} and angular directives?

Actually I'm confused between when to use {{ }} when using angular directives and when to not to use {{ }}
For example:
<div data-ng-init="isHidden=false">
<div data-ng-show="isHidden">
...
</div>
</div>
and
<div data-ng-init="isHidden=false">
<div data-ng-show="{{isHidden}}">
...
</div>
</div>
I'm confused between these syntax ? What are the differences between those? And when to use what? Thanks in advance :)
There is no difference except the "look" u need to use the {{value}} syntax in case you want to write data anywhere in your html body
<div>{{value}}</div>
It's all explained here: Difference between double and single curly brace in angular JS?
For quick answer:
{{}} are Angular expressions and come quite handy when you wish to
write stuff to HTML
Don't use these at a place that is already an expression!
For instance, the directive ngClick treats anything written in between
the quotes as an expression
<div data-ng-init="isHidden=false">
<div data-ng-show="isHidden">
...
</div>
</div>
In This Situation data-ng-show = false , Takes From data-ng-init As Statically,if You Have Given true Then It Returns True .
But Here
<div data-ng-init="isHidden=false">
<div data-ng-show="{{isHidden}}">
...
</div>
{{}} Called As Expressions In Angular One Of The Most Important Concept
It Directly Evaluate If isHidden = true Or False Based On Any Condition Written In Your App.js File .
Example:
<div data-ng-init="isHidden=YourVariable">
<div data-ng-show="{{isHidden}}">
...
</div>
if(YourVariable == true){
Do Somthing
}
else{
Do Something
}
If you are asking when to use {{}} while assigning value to a attribute and when not.
It depends on the binding types of directive. '#' or '='
So here, you have to use:
data-ng-show="{{isHidden}}" if the binding type of directive scope data-ng-show is '#', that mean the data-ng-show will be expecting a string value. So in this case if you keep data-ng-show="isHidden" it will take data-ng-show's value as 'isHidden', but data-ng-show="{{isHidden}}" will take the value of the $scope.isHidden and assign to data-ng-show.
Now if the binding type of directive scope data-ng-show is '=', that means the data-ng-show will be expecting a value from a scope. So data-ng-show="isHidden" itself will take the value of he $scope.isHidden and assign to data-ng-show.
Note: all the default HTML attributes expect a string so you have to use {{}} for default HTML attributes.
There is no as such major difference unless one uses them in the DOM for the value.
When one uses the following:
<div data-ng-show="isHidden">
then, expression is evaluated and on the basis of it respective value, the ng-show either hides or displays the div. But the value of the isHidden cannot be seen, when one inspects the HTML using the browser developer tool.
When one uses the following:
<div data-ng-show="{{isHidden}}">
In this case, the value of the isHidden can be seen from the developer tools, and the rest of the expression does evaluates the same as that of (1).

Variable value as directive/controller name inside template (with $compile/$interpolate)?

I am creating a directive in which template I need to use the a scope's variable value as the name of the directive (or alternatively controller) to load.
Say I have a directive widget that has a template called widget.html which looks like:
<div class="widget widget.type" {{widget.type}} ng-controller="widget.type">
<div class="navBar">
<div ng-include="widget.type + '-t.html'"></div>
<i class="fa fa-close"></i>
<hr>
</div>
<div ng-include="widget.type + '-f.html'"></div>
</div>
Now widget.type is not getting evaluated in the first line. It works fine for ng-include. Say widget.type's value is weather. The first line should then be interpolated first to look like (doesn't matter if class attribute, widget.type-attr or ng-controller is interpolated)
<div class="widget" weather>
and then compiled to include the weather directive.
How can I get widget.type interpolated in the template?
Not an option is to use ng-include to load the directive. I need to use one common template for the widget and want to add/override/extend the base directive with additonal functionality/Variables.
If this is not the way to achieve that, is there a way to extend a directive the OOP-way?
See the plunkr
You can only place interpolation expressions in text nodes and attribute values. AngularJS evaluates your template by first turning it into DOM and then invoking directive compilation, etc. If you try to place {{...}} instead of attribute name, you'll just end up with messed-up DOM.
If you really need to replace a whole directive based on $scope variable value, you'll need to create a directive for application of other directives and do some heavy lifting with $compile (you'll have to completely re-compile the template each time the value changes). I'd recommend trying to find other designs solving your situation before attempting this.
For adjusting your template based on element attributes, see this answer.

AngularJS : How to properly transclude child elements in custom directive?

Here's some code : link
I'm trying to create a directive that wraps its children in some boilerplate. But if the children have ng-if controlling their appearance, the "transclusion" doesn't work. Well it sort of does, but as you can see the ng-if logic is not getting passed through correctly.
I'd like to know how to fix it, but also where (if anywhere) this is described in the Angular docs.
The problem is that Angular initially replaces ngIf with a comment that it uses to track where to place the conditional code. It's easiest to see with an example.
Your HTML:
<div control-group>
<label>Test</label>
<input type="text" ng-model="editing.name" />
<span class="text-error" ng-if="editing.name.length != 3"> Name must be 3 characters </span>
</div>
Looks like this inside your transclude function's cloned variable (transclude(function (cloned) {):
<div control-group>
<label>Test</label>
<input type="text" ng-model="editing.name" class="ng-valid ng-dirty">
<!-- ngIf: editing.name.length != 3 -->
</div>
So, the element with class text-error that you're filtering on (below) isn't in cloned. Just the comment is there.
var inputsAndMessages = cloned.filter('input, button, select, .text-error');
Since you're only transcluding elements that match the above filter the ngIf comment is lost.
The solution is to filter for comments as well and add them in your append (so Angular maintains it's reference point to the ngIf). One way to do that is to replace the above with this (using the fact that an html comment is node type 8)
var messages = cloned.filter(function(){ return this.nodeType == 8; }); //comments
var inputs = cloned.filter('input, button, select')
var inputsAndMessages = inputs.add(messages);
Working plunker
You need to tell the directive where to place child elements with the ng-transclude directive: (plnkr)
template: "<div class='control-group' ng-transclude>" +
"<label class='control-label' for=''></label>" +
"<div class='controls'></div>" +
"</div>",
From the docs:
Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
I wasn't sure what exactly your intent was since you have the input and label both in the template and as children in the HTML. You might want to place your ng-transclude elsewhere.

ngSwitch slider directive not working

I'm trying to create a slider directive inpired by this example using ngSwitch and ngAnimate but without success. There is a timeout calling the next function that increments the scope.current variable and ngSwitch should use this variable to switch images.
<div class="slider">
<img src="..." />
<img src="..." />
<img src="..." />
</div>
Here is my plunker with the example. I think it is something related with the scope(its always you scope).
UPDATE: I did some progress, I moved the attributes manipulation to the compile function and it seems it helped a bit but now I get an error: No controller: ngSwitch.
new plunker
The reason why Angularjs shows the error No controller: ngSwitch is because ng-switch-when depends on ng-switch. So you should add the attribute of ng-switch-when when the ng-switch scope is created. The easiest way is to use $observe on the ngSwitch.
Here is one way to make the compile work
attrs.$observe('ngSwitch', function () {
for (i = _i = 0, _len = pages.length; _i < _len; i = ++_i) {
page = pages[i];
page.setAttribute('ng-switch-when', i);
}
});
However the animation will not work since you can't access the current value due to the new scope ng-switch created.
In order to make the directive work, I think the easiest way is to declare the ng-switch="current" in the template so the directive can access to the the value it watches on.
Hope it can shed some light on.
I have a workaround using ng-if instead of ng-switch, see here.
If you want to stick around using ng-switch, I would suggest you give a closer look to the scope created by ng-switch (see this).

Resources