I have a view where a parent div has ng-if on it, and some child element has ng-show on it. It seems that the ng-show isn't working correctly when nested under an element with ng-if on it. Is this an Angular bug or am I doing something wrong? See this plunker.
The HTML:
<!-- with ng-if on the parent div, the toggle doesn't work -->
<div ng-if="true">
<div>
visibility variable: {{showIt}}
</div>
<div ng-show="!showIt">
<a href="" ng-click="showIt = true">Show It</a>
</div>
<div ng-show="showIt">
This is a dynamically-shown div.
<a href="" ng-click="hideIt()">Hide it</a>
</div>
</div>
<br/><br/>
<!-- with ng-show on the parent div, it works -->
<div ng-show="true">
<div>
visibility variable: {{showIt}}
</div>
<div ng-show="!showIt">
<a href="" ng-click="showIt = true">Show It</a>
</div>
<div ng-show="showIt">
This is a dynamically-shown div.
<a href="" ng-click="hideIt()">Hide it</a>
</div>
</div>
The JavaScript:
scope.hideIt = function () {
scope.showIt = false;
};
Thanks,
Andy
ng-if is better in this regard. Using it in place of ng-show will prevent the heavy content from being rendered in the first place if the expression is false. However, its strength is also its weakness, because if the user hides the chart and then shows it again, the content is rendered from scratch each time.
Basic Difference between ng-if, ng-show and ng-hideng-if can only render data whenever the condition is true. It doesn't have any rendered data until the condition is true. ng-show can show and hide the rendered data, that is, it always kept the rendered data and show or hide on the basis of that directives.
Note that when an element is removed using ngIf its scope is destroyed and a new scope is created when the element is restored. The scope created within ngIf inherits from its parent scope using prototypal inheritance.
AngularJS ng-show DirectiveThe ng-show directive shows the specified HTML element if the expression evaluates to true, otherwise the HTML element is hidden.
Nemesv mentioned above that you should use $parent
, but although working, this is not the right solution. The problem with this solution is:
ng-if
and the controller scope.ng-if
to ng-show
will break your code.$parent.$parent.$parent....
)The quick correct solution is to not define showIt
directly on your scope, but instead place it in an object (e.g. component.isVisible
).
To understand why this seemingly counter-intuitive solution works and is indeed the correct one you first need to know a little more about how inheritance works with angular:
Scopes inherit from each other using prototypal inheritance, which is the form of inheritance build in into Javascript. This looks as followed:
var myScope = {
showIt : false
}
var ngIfScope = {};
nfIfScope.__proto__ = myScope;
When you now get a property on the ngIfScope
object that is not present there it will look in it's prototype to find it there. So if you request ngIfScope.showIt
the browser does something like this:
if (ngIfScope.hasOwnProperty("showIt")) {
return ngIfScope.getOwnProperty("showIt"); // getOwnProperty does not actually exist in javascript
} else {
return ngIfScope.__proto__.showIt;
}
(in reality this happens recursively, but that's unimportant for this example).
Setting a property is much more straightforward though:
ngIfScope.setOwnProperty("showIt", newValue);
Now we have this information we can see what actually went wrong with your original implementation.
We started with the following scopes:
var myScope = {
showIt : false
}
var ngIfScope = {};
ngIfScope.__proto__ = myScope;
When the user clicks the show button the following code is executed:
ngIfScope.showIt = true;
and the resulting scopes are:
var myScope = {
showIt : false
}
var ngIfScope = {
showIt : true
}
ngIfScope.__proto__ = myScope;
As you can see the new value is written in ngIfScope
, and not in myScope
as you probably expected. The result is that ngIfScope.showIt
overshadows the variable from myScope
and myScope.showIt
isn't actually changed at all.
Now lets see what happens if we place the visibility trigger in an object.
We begin with the new scopes:
var myScope = {
component : {
isVisible : false
}
};
var nfIfScope = {};
ngIfScope.__proto__ = myScope;
Not much changed so far. But now lets see what happens when the user clicks the button:
ngIfScope.component.isVisible = true;
With some helper variables we can see how this is executed in the browser:
var tempObject = ngIfScope.component;
tempObject.isVisible = true;
The first line here is a get operation. Since component
is not defined on ngIfScope
the Javascript engine will look at the prototype of ngIfScope
(myScope
) to find it there, as I explained above. Thus:
tempObject === ngIfScope.__proto__.component === myScope.component
We are now changing values directly on myScope.component
, and hence the variables are not overshadowed this time. Resulting scopes are:
var myScope = {
component : {
isVisible : true
}
};
var ngIfScope = {};
var ngIfScope.__proto__ = myScope;
We now have a working implementation without explicitly binding to a $parent
scope, and thus there is no (or little) coupling between the scopes. Prototypal inheritance does the work for us, and nested scopes work right out of the box as well.
Unlike ng-show
the ng-if
directive creates a new scope.
So when you write showIt = true
inside the ng-if
you are setting the showIt
property on your child scope and not on your main scope.
To fix it use the $parent
to access your property on your parent scope:
<div ng-if="true">
<div>
visibility variable: {{showIt}}
</div>
<div ng-show="!showIt">
<a href="" ng-click="$parent.showIt = true">Show It</a>
</div>
<div ng-show="showIt">
This is a dynamically-shown div.
<a href="" ng-click="hideIt()">Hide it</a>
</div>
</div>
Demo Plunker.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With