ng-show not working inside ng-if section - angularjs

ng-show is not working in following code
<button ng-click="itemIndex=0;showHome=true;" class="btn btn-link">Home</button>
<div ng-if="itemIndex==0">
<div ng-show="showHome">{{showHome}}
<h3>Home Section</h3>
<img ng-click="showHomeItems=showHomeItems?false:true;showHome=false;" ng-src="images/home.png"/>
</div>
<div ng-show="showHomeItems">
Home Items{{showHome}}
</div>
</div>
Without ng-if it is working fine but with ng-if its not working.

Read this article.
There are some directives in Angular that create a child scope, like ng-repeat, ng-if.
Inside those scopes, the booleans (such as showHome) are searched only within that new scope, and not in the parent scope.
In order to avoid such bugs, it's considered best practice to place the logic in the controller (or service, just not in the HTML) inside an object, which is not a primitive variable and will be looked up in the scope prototypical chain.

Try declaring a variable like this:
$scope.switch = {showHome : true}
and then use it in the html like this:
<div ng-show = "switch.showHome" >

Related

ng-keyup firing but not reading properly when used with an ng-if inside a directive template

I have a directive and it works fine in a way such that when I type something the search() scope function inside my directive fires and sets $scope.query with the input text.
here is the directive template
<div class="container">
<div class="system-filter-header">
<div class="no-gutter">
<div class="system-search-wrapper search-wrapper-width">
<i ng-click="search($evt)" class="fa fa-search"></i>
<input type="text" ng-keyup=search($evt) class="search pull-left suggesstions-styles"
ng-model="query" ng-attr-placeholder="Search...">
</div>
</div>
</div>
</div>
here is the scope function which gets triggered
$scope.search = function() {
console.log($scope.query.length)
}
But when I used an ng-if="true" in first line of template (true used for generalizing only, I want to do a different conditional check inside ng-if) such that,
<div class="container" ng-if="true">
still the search gets triggered but the console.log gives always 0 and it doesn't seem to update the $scope.query value as it stays as $scope.query = ''
throughout the typing.
EDIT
Here is a an example codepen with almost similar behaviour. The problem is with the searchBox directive and I have added ng-if=true to the template but searching doesn't work. When I remove the ng-if searching works fine.
Any reason for this?
Rule of thumb in AngularJS: your ng-model should always include a dot. Otherwise AngularJS directives that create child scopes (like ng-if or ng-repeat) will create a duplicate property on that child scope instead of the parent scope. Following the controllerAs convention completely mitigates this behavior.

Angular - Changing scope is not getting reflected

This is weird as it should be pretty straightforward. I will post my code first and then ask the question:
html -
<div ng-controller="myController" ng-switch on="addressCards">
<div>
{{addCustom}} // does not get changed
<div ng-if="addCustom === false">
{{addCustom}} // does get changed
<button type="button" class="btn btn-primary btn-icon-text" ng-click="addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
controller -
(function(){
'use strict';
angular.module('myApp')
.controller('myController',['$scope',myController]);
function myController($scope){
$scope.addCustom = false;
}
})();
So I simply introduced a scope variable - addCustom - in my controller and set it to false as default. This variable controls if a div is shown or not. I am also outputting the value of the scope on the html at 2 different locations. Please see above.
But when I change its value in an ng-click within this divs, its value is changing at the second location(within the div) but not the first one(outside the div). Because of this the div does not change state as well.
I am not able to figure what might be possibly wrong here. Can someone please help?
The thing happening is when you have ng-repeat,ng-switch and ng-if directive, angular creates child scope for those element wherever they are placed. Those newly created scope are prototypically inherited from there parent scope.
On contrast Prototypal Inheritance means?
If you have scope hierarchy, then parent scope property are accessible inside child scope, only if those property are object (originally object referenced is passed to child scope without creating its new reference). But primitive datatypes are not accessible inside child scope and if you looked at your code addCustom scope variable is of primitive dataType.
Lets discuss more about it.
Here you have myController controller which has addCustom scope variable of primitive type & as I said above ng-switch & ng-if directive are compiled they do create new child scope on that element. So in your current markup you have ng-switch on ng-controller="myController" div itself. For inner html it had created a child scope. If you wanted to access parent scope inside child(primitive type) you could use $parent notation before scope variable name. Now you can access the addCustom value by $parent.addCustom.
Here its not over when angular compiler comes to ng-if div, it does create new child scope again. Now inner container of ng-if will again have child scope which is prototypically inherited from parent. Unfortunately in your case you had primitive dataType variable so you need to use $parent notation again. So inside ng-if div you could access addCustom by doing $parent.$parent.addCustom. This $parent thing will solve your problem, but having it on HTML will make unreadable and tightly couple to its parent scope(suppose on UI you would have 5 child scope then it will look so horrible like $parent.$parent.$parent.$parent). So rather you should go for below approach.
Follow Dot rule while defining ng-model
So I'd say that you need to create some object like $scope.model = {} and add addCustom property to it. So that it will follow the prototypal inheritance principle and child scope will use same object which have been created by parent.
angular.module('myApp')
.controller('myController',['$scope',myController]);
function myController($scope){
$scope.model = { addCustom : false };
}
And on HTML you will use model.addCustom instead of addCustom
Markup
<div ng-controller="myController" ng-switch on="addressCards">
<div>
{{model.addCustom}} // does not get changed
<div ng-if="model.addCustom === false">
{{model.addCustom}} // does get changed
<button type="button" class="btn btn-primary btn-icon-text" ng-click="model.addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
Other best way to deal with such kind of issue is, use controllerAs pattern while using controller on HTML.
Markup
<div ng-controller="myController as myCtrl" ng-switch on="addressCards">
<div>
{{myCtrl.addCustom}} // does not get changed
<div ng-if="myCtrl.addCustom === false">
{{myCtrl.addCustom}} // does get changed
<button type="button" class="btn btn-primary btn-icon-text" ng-click="myCtrl.addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
From the Docs:
The scope created within ngIf inherits from its parent scope using prototypal inheritance. An important implication of this is if ngModel is used within ngIf to bind to a javascript primitive defined in the parent scope. In this case any modifications made to the variable within the child scope will override (hide) the value in the parent scope.
-- AngularJS ng-if directive API Reference
The rule of thumb is don't bind to a primitive, instead bind to an object.
Scope inheritance is normally straightforward, and you often don't even need to know it is happening... until you try 2-way data binding (i.e., form elements, ng-model) to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope. It doesn't work the way most people expect it should work. What happens is that the child scope gets its own property that hides/shadows the parent property of the same name. This is not something AngularJS is doing – this is how JavaScript prototypal inheritance works. New AngularJS developers often do not realize that ng-repeat, ng-if, ng-switch, ng-view and ng-include all create new child scopes, so the problem often shows up when these directives are involved. (See this example for a quick illustration of the problem.)1
This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models – watch 3 minutes worth. Misko demonstrates the primitive binding issue with ng-switch.1
Ng-if introduces a different scope. Try this as an attribute of your button:
ng-click="$parent.addCustom = false"
This will assure that you're accessing the same scope.
It's because of this that it's always good practice to use the ControllerAs syntax. All attributes are bound to the controller object and namespaced accordingly, meaning you never run in to this problem. I've updated your example using the ControllerAs syntax to demonstrate its use.
HTML
<div ng-controller="myController as vm" ng-switch on="addressCards">
<div>
{{vm.addCustom}}
<div ng-if="vm.addCustom === false">
{{vm.addCustom}}
<button type="button" class="btn btn-primary btn-icon-text" ng-click="vm.addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
Controller
(function(){
'use strict';
angular.module('myApp')
.controller('myController', [ myController ]);
function myController () {
var vm = this;
vm.addCustom = false;
}
})();
Here is an excellent article providing more detail about ControllerAs and it's advantages.
Both Classic Controller and Controller As have $scope. That's super important to understand. You are not giving up any goodness with either approach. Really. Both have their uses.

using ng elements outside of ng-controller

I am inexperienced with angular.
I am using angular to create a series of nested divs (a form) on a webpage. The top div has ng-controller="controllername" as an attribute. Within the nested divs is a div with ng-show="showvar" as an attribute.
It looks like this.
<div class="page">
<div ng-controller="controllername">
<div ng-show="showvar">Hidden Stuff</div>
</div>
</div>
When I perform functions on showvar to make it true, the div appears (and disappears when false) as intended.
I also have a completely separate div 'outside' the the original nest of divs with the ng-controller attribute. As such, there is no ng-controller attribute in this seperate hierarchy BUT I have nested another div inside with the ng-show="showvar" attribute.
Updated HTML structure is as such
<div class="page">
<div ng-controller="controllername">
<div ng-show="showvar">Hidden Stuff</div>
</div>
<div class="seperate">
<div ng-show="showvar">More Hidden Stuff</div>
</div>
</div>
When the page loads, both divs with ng-show="showvar" in the separate nests are hidden as ng-hide has been appended by angular. When I perform functions on showvar after the page load to make it true, only the div within the ng-controller div gets shown.
I (think I) understand this is because the ng elements are evaluated at page load (and appended with ng-hide, even outside the controller?) but only the ng elements within the div with the ng-controller attribute are evaluated when functions are performed after page load. Is this correct?
How can I get the other ng-show to be evaluate 'outside' of the ng-controller div?
I was thinking one option is to append ng-controller to the overall 'page' div instead of the nested div. But what other options do I have?
EDIT: I also tried simply adding ng-controller="controllername" to the separate div. I guess angular 'ignores' the duplicate ng-controller div?
The problem your facing is that the showvar resides in your controller's scope, your second usage of the showvar is not within that scope.
What you need to do is make sure the variable is available where needed.
Say you add the variable to the parentController (you don't have one in your example so I'll add one)
<div class="page" ng-controller="parentController">
<div ng-controller="controllername">
<div ng-show="showvar">Hidden Stuff</div>
</div>
<div class="seperate">
<div ng-show="showvar">More Hidden Stuff</div>
</div>
</div>
app.controller('ParentController', function($scope){
$scope.showvar = false;
});
problem with this is when you set showvar to true within your controllername controller it will set it in the innerscope and not the outer. When making sure you have the right scope by accessing it through another object you should be safe.
So try it like this:
<div class="page" ng-controller="parentController">
<div ng-controller="controllername">
<div ng-show="obj.showvar">Hidden Stuff</div>
</div>
<div class="seperate">
<div ng-show="obj.showvar">More Hidden Stuff</div>
</div>
</div>
app.controller('ParentController', function($scope){
$scope.obj = {
showvar: false
}
});
Quick demo
Your issue here is that you ended with 2 "showvar" variables: one within the "controllername" scope and another one on the app scope (as you have a ng-app declaration somewhere in your html parent of the "page" div).
When you load your page, you get the value of "showvar" in the controller scope for the first div, and for the "separate" one, you get the "showvar" variable in the app scope, which doesn't exist, therefore it is resolved to "false" (even though angular declares it for you in your app scope and you can even modify its value later).
When you change the value of "showvar" in the controller scope, it doesn't change the one in the app scope, making the "separate" div stay hidden forever =)

Angular access controller scope from nested directive

this fiddle represents what i am trying to do:
http://jsfiddle.net/d1001001/dwqw6/.
The grid directive needs to grab some data from controller, but since it's nested in the modal directive, which has isolated scope, it doesn't have access to controller's scope. If i
put it like this
<div ng-controller="MyCtrl">
<grid data="data" cols="cols"></grid>
</div>
it works.
Is there a solution to this? I don't feel like passing the data and cols variables to the modal directive as well. Thanks
Use
<div ng-transclude></div>
instead of
<ng-transclude></ng-transclude>
in the template of modal directive.

Jasmine-jquery, testing generated GUI for (nested) directives

I recently started using jasmine-jquery (1.3.1) together with angular (1.0.6) and need an advice on testing GUI.
I have some view for controller, which has angular directives, like 'view.html':
<div id='main-div'>
<div my-directive objects-to-iterate='someScopeObjects'>
<span id='default-span'>some default text</span>
</div>
</div>
, and in JS a directive "myDirective" is defined, having template-by-url 'myDirective.html' which includes ng-repeat and makes some nested div (row) foreach in objectsToIterate.
I load fixtures for 'view.html' and 'myDirective.html'.
Now I would like to test that during user interaction there are really some elements (rows) in 'myDirective' repeated block.
Beginning was simple:
var div = $('div#main-div');
div = $compile(div)(scope);
scope.$digest();
expect(div).toBeVisible();
And now I'm stuck. Cannot get access to "generated" source of 'myDirective' element. If I use
div.find('whatever-selector-for-element-my-directive'),
I get
<div my-directive objects-to-iterate='someScopeObjects'>
<span id='default-span'>some default text</span>
</div>
If I try to compile+digest this html against scope, I still get same markup. How could I get "generated" source, looking like (pseudo)
<div id='my-directive-content'>
<div id='object-1'>blah</div>
<div id='object-2'>blah</div>
</div>
in order to check if there are N rows visible to user?
someScopeObjects in scope exist and are valid.
And my next step is actually testing same thing for directives nested inside of 'my-directive', so I somehow have to get access to the scope of 'my-directive'. How? :)
Thank you.

Resources