Scope changes after ng-switch - angularjs

I have a switch that changes the value of a variable called p.
<div ng-switch on="p">
<div ng-switch-when="true">
/*...show nodes and ability to add node*/
</div>
<div ng-switch-when="false">
/*for now show nothing*/
</div>
</div>
In my controller:
$scope.nodes=[{node1},{node2},{node3}];
function to add a node
$scope.$watch('nodes', function(nodes) {
console.log(nodes);
console.log("================");
},true);
PROBLEM: when i print $scope nodes in the above all the new nodes are shown. If I switch OFF and ON (p=false and then p=true) I have the initial $scope.nodes. Why on earth are my nodes reset on switch?
See this example: plunker
EDIT: ng-switch worked with no scope change if I didn't have a directive but reinitialized my scope when I used a directive inside it. Although I haven't understood exactly why I dropped ng-switch and used ng-show instead.

It would help to see the controller and on which level it is initialized. The ngSwitch will not just hide the content - it will remove and add the html and initialize the controllers and directives each time a switch is made. Probably the nodes get initialized there as well.

Related

Why don't child elements show inside an ng-if div when it's true?

I have an ng-if div as follows:
<div ng-if="colorSelected" ng-cloak>
<h6>My Color</h6>
</div>
When colorSelected is true, the inner header element never shows up. Am I doing something wrong?
Use ng-show instead of ng-if.
ng-if creates it's own scope so any scope variables you use inside of an ng-if will be related to a different scope. For example...
<div ng-if='selectedColors'>
{{iAmDefinedInAController}}
</div>
The variable 'iAmDefinedInAController' will be empty even if it is defined in the controller because a new scope is created inside the ng-if.
ng-show does not do this, so just use that and you will be good to go.
it should word.
Probably your modal 'colorSelected' is not binded.
use "ng-if = true" and see if it works

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

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.

AngularJs unable to use custom directive inside tr

I've just started looking at AngularJs. I was attempting to use a custom directive inside a tr element. I get the following error regarding the switch directive
Controller 'ngSwitch', required by directive 'ngSwitchWhen', can't be found!
Some sample code is here: http://plnkr.co/edit/YiSFYK5l8mNIlBo6OGFW
Even after I removed the swtich it still doesn't seem to do anything. I changed the repeat direct to be over currentSheetData and removed the swtich entirly but there's no code in the rows.
However in my example I do the same setup inside a div element and it works fine. Would someone explain what I'm doing incorrectly
You need ng-switch directive on the parent node before declaring ng-switch-when on the child node.
Example:
<div class="animate-switch-container"
ng-switch on="selection">
<div class="animate-switch" ng-switch-when="settings">Settings Div</div>
<div class="animate-switch" ng-switch-when="home">Home Span</div>
<div class="animate-switch" ng-switch-default>default</div>
</div>
https://docs.angularjs.org/api/ng/directive/ngSwitch

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.

Difference between onLoad and ng-init in angular

I am learning angular. I don't understand what is difference between onLoad and ng-init for initialization of a variable. In which scope it creates this variable.
For example
<ng-include onLoad="selectedReq=reqSelected" src="'partials/abc.html'"></ng-include>
OR
<ng-include ng-init="selectedReq=reqSelected" src="partials/abc.html"></ng-include>
Please also give me some idea about isolated scope.
ng-init is a directive that can be placed inside div's, span's, whatever, whereas onload is an attribute specific to the ng-include directive that functions as an ng-init. To see what I mean try something like:
<span onload="a = 1">{{ a }}</span>
<span ng-init="b = 2">{{ b }}</span>
You'll see that only the second one shows up.
An isolated scope is a scope which does not prototypically inherit from its parent scope. In laymen's terms if you have a widget that doesn't need to read and write to the parent scope arbitrarily then you use an isolate scope on the widget so that the widget and widget container can freely use their scopes without overriding each other's properties.
From angular's documentation,
ng-init SHOULD NOT be used for any initialization. It should be used only for aliasing.
https://docs.angularjs.org/api/ng/directive/ngInit
onload should be used if any expression needs to be evaluated after a partial view is loaded (by ng-include).
https://docs.angularjs.org/api/ng/directive/ngInclude
The major difference between them is when used with ng-include.
<div ng-include="partialViewUrl" onload="myFunction()"></div>
In this case, myFunction is called everytime the partial view is loaded.
<div ng-include="partialViewUrl" ng-init="myFunction()"></div>
Whereas, in this case, myFunction is called only once when the parent view is loaded.
Works for me.
<div ng-show="$scope.showme === true">Hello World</div>
<div ng-repeat="a in $scope.bigdata" ng-init="$scope.showme = true">{{ a.title }}</div>

Resources