Angular two different include inside controller have different $scope - angularjs

<!-- index.html -->
<div ng-controller="navbarController">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div ng-include src="'./html/bars.html'"></div>
<div ng-include src="'./html/extendBars.html'"></div>
</div>
</div>
<nav class="navbar navbar-default navbar-fixed-bottom" role="navigation">
<div class="container">
This is my cool bottom bar :)
</div>
</nav>
</div>
<!-- ./html/bars.html -->
<a class="navbar-brand" ui-sref="Welcome">Brand</a>
<ul class="nav navbar-nav">
<li ng-class="{ active: TopMenu=='OurWork'}" ng-click="TopMenu='OurWork'"><a ui-sref="OurWork">Our Work</a>
</li>
</ul>
<!-- ./html/extendBars.html -->
<ul class="nav navbar-nav">
<li ng-class="{ active: TopMenu=='ProjectManager'}" ng-click="TopMenu='ProjectManager'"><a ui-sref="ProjectManager">Projects Manager</a>
</li>
<li ng-class="{ active: TopMenu=='Publish'}" ng-click="TopMenu='Publish'"><a ui-sref="Publish">Publish</a>
</li>
<li>Logout
</li>
</ul>
The problem is that this two files that contain two part of navbar have different $scope, when i change value of TopMenu it changes only on one of two files. In my js controller code when i define $scope.TopMenu is defines correct on both files. Why this is happening?

ng-include creates a child scope. since you're using two ng-includes, a new child scope is created for each, both prototypically inheriting from the parent scope (the navbarController).
by prototypical inheritance, any objects on the parent will be seen in the child scopes (as long as a blocking object hasn't been created on the child). however, setting a value on the child scope won't be seen on the other child, as it's not part of it's inheritance chain (it only climbs directly up).
from the child scopes, you could access $rootScope instead of $scope in order to access the values above. alternately, you could encapsulate the data you're wanting to share between the scopes in a Service (in most cases, this is the preferred method to keep things clean).
EDIT to include function based approach based on question in comments:
instead of trying to directly set and access values on the parent scope, a quick alternative would be to create functions in your navbarController to handle this. a service is potentially cleaner, especially if you're using this from other areas in the app, but something like this should work (I didn't create a plnkr to test, and i obviously don't know what's in your existing navbarController -- so this is just a concept).
in your controller file:
.controller('navbarController', function($scope) {
var currentMenu = 'defaultMenuName';
$scope.setMenu = function(newMenu) {
currentMenu = newMenu;
};
$scope.isMenu = function(testMenu) {
return currentMenu == testMenu;
};
}
in your html:
<li ng-class="{ active: isMenu('OurWork')}" ng-click="setMenu('OurWork')"><a ui-sref="OurWork">Our Work</a>
Example of this approach in action:
http://plnkr.co/edit/eQKbjjRVdSqDjLlpLPCB?p=preview

Related

Expression in ng-if of AngularJS

Consider the following snippet
ng-if not working
<div class="row">
<div class="col-md-8">
<ul>
<li ng-repeat="bigL in bigLs">
<span ng-if="isObj(bigL)">{{bigL.note}}</span>
<ul ng-if="bigL instanceof Array">
<li ng-repeat="bigLl in bigL">
{{bigLl}}
</li>
</ul>
</li>
</ul>
</div>
</div>
ng-if working
<div class="row">
<div class="col-md-8">
<ul>
<li ng-repeat="bigL in bigLs">
<span ng-if="isObj(bigL)">{{bigL.note}}</span>
<ul ng-if="isArr(bigL)">
<li ng-repeat="bigLl in bigL">
{{bigLl}}
</li>
</ul>
</li>
</ul>
</div>
</div>
Controller
$scope.isArr = function(bigL){
return bigL instanceof Array;
};
I use ng-if to determine whether a nested ul is required to create (by determinate different data type inside the array bigLs), I come to a situation that ng-if cannot evaluate bigL instanceof Array, I then move this snippet inside a function, with the same context, the ng-of works properly, but still cannot understand why it is a need to wrap the expression inside a function instead of running it directly inside the ng-if.
Appreciate for any clarification, thanks!
I'm not exactly sure of the problem, but there are several things that have bad smells in your code:
Don't use 'instanceof Array', ever. It won't work in an angular expression.
Instead, use angular.isArray(). This will only work in javascript by adding a method to your scope.
So, you would want to do something like this:
Controller:
...
$scope.hasChildren = function(bigL1) {
return angular.isArray(bigL1);
}
Template:
...
<ul ng-if="hasChildren(bigL)">
...
As a bonus, it becomes much easier to unit test this code.

Submit and disable all buttons in an ng-repeat without adding a new property on the scope

I have a list of items where each item is clickable and should trigger the submit(id) function. I would like to disable all items (buttons) when one of them is clicked.
<ul>
<li ng-repeat="item in items">
<button ng-click="submit(item.id)">Submit</button>
</li>
</ul>
I could define $scope.submitted variable in a controller and then set ng-disabled="submitted" in my view. I could also wrap it into a <form> and use frm.$submitted. Well... I would like to define everything inside view.
I search for an elegant solution where a don't have to define a $scope variable to achieve this. What do you propose?
If you do not want to define a property in the controller, and assuming that submit function never fails to submit. You could utilize the items array itself, by adding a property when click happens and after submit function is run.
<ul>
<li ng-repeat="item in items">
<button ng-click="submit(item.id); items.submitted=true"
ng-disabled="items.submitted">Submit</button>
</li>
</ul>
With this you are adding a property in the array items (so that it is available across child scopes of ng-repeat as well as items is set on its parent scope) so essentially you are not defining a new scope variable for this.
angular.module('app',[]).controller('ctrl', function($scope){
$scope.items = [{id:1},{id:2},{id:3},{id:4}]
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<ul>
<li ng-repeat="item in items">
<button ng-click="submit(item.id); items.submitted=true"
ng-disabled="items.submitted">Submit</button>
</li>
</ul>
</div>

Uncertainty about the use of ngInclude

I render recursive template with ngInclude. Here is my code:
<!--
definitionsRenderer.html
-->
<ul data-ng-if="currentLoc.definitions && templateIsloaded">
<li ng-repeat="definition in currentLoc.definitions">
{{definition.text}}
</li>
</ul>
<ul data-ng-if="templateIsloaded">
<li ng-repeat="loc in currentLoc.loc" ng-include="'./partials/definitionsRenderer.html'"></li>
</ul>
I need code to be executed asynchronous. So for scope of every included template
I want to have a variable - templateIsloaded with value false by default. When template is loaded and rendered it value to be true. How do that?
Best regards.
To have separate $scope for each of recursive called includes you can simply add ng-controller.
<li ng-repeat="loc in currentLoc.loc" ng-controller="definitionsRendererCtrl" ng-include="'./partials/definitionsRenderer.html'"></li>

Call another controller from inside ng-repeat

I am new to angular and I am trying to call a function from another Controller. I do not want to define an addIssue function in ItemsController which calls IssuesController.addIssue or share a service, but directly reference the IssuesController. Is there any way to do that?
Here is my sample code:
<div ng-controller="ItemsController">
<ul ng-repeat="item in items">
<li><a href ng-click="addIssue(item)">{{item.name}}</a></li>
</ul>
</div>
<div ng-controller="IssuesController">
<ul ng-repeat="issue in issues">
<li>{{issue.name}}</li>
</ul>
</div>
<script>
angular.module('app').controller("IssuesController", function () {
$scope.issues = [];
$scope.addIssue = function (item) {
// Add Issue
}
});
</script>
UPDATE:
What if I have a third base controller to help them share $scope, how will that work?
<div ng-controller="OrdersController">
<div ng-controller="ItemsController">
<ul ng-repeat="item in items">
<li><a href ng-click="addIssue(item)">{{item.name}}</a></li>
</ul>
</div>
<div ng-controller="IssuesController">
<ul ng-repeat="issue in issues">
<li>{{issue.name}}</li>
</ul>
</div>
</div>
As EliteOctagon mentioned, there are other and probably more "best practice" ways to achieve your goal but you can always do this the "dirty" way, you can use $rootScope in your ItemsController to call the addIssue(item) function on IssuesController, and since all other scopes are descendant scopes of the $rootScope you can use it in any controller.
Example: http://jsfiddle.net/choroshin/5YDJW/2/
There is, but it's such a terrible idea that you should think about it thoroughly. A much better solution would be to put that functionality into a parent controller. Obviously you have functionality that is much broader in scope (pun intended) then just that div with the IssuesController.
Provided your code is a realistic sample, then addIssue() is not even used there. So it wouldn't even make sense to put it there.
One way to directly access addIssue is:
angular.element(/*select your div*/).scope().addIssue(item);
How you select your div depends. If i has an id you can use that. If you use jQuery then you can use [ng-controller= ItemsController].
Why don't you nest the controllers? You can read the Scope Inheritance Example at http://docs.angularjs.org/guide/controller
<div ng-controller="ItemsController">
<ul ng-repeat="item in items">
<li><a href ng-click="addIssue(item)">{{item.name}}</a></li>
</ul>
<div ng-controller="IssuesController"> <!-- nested and will inherit scope -->
<ul ng-repeat="issue in issues">
<li>{{issue.name}}</li>
</ul>
</div>
</div>
so if you have a function in ItemsController
$scope.addIssue = function() {do something}
then the IssuesController will inherit the $scope and you'll be able to use that function.

How to change the content of a tab while you are on the same tab using AngularJS and Bootstrap?

Using AngularJS and Bootstrap, let say there are 3 tabs: tab1, tab2, and tab3. There are also some links on each tabs. Now for example, tab1 is active. The question is: how to change the content of the tab1 by clicking a link within the same tab?
main.html:
<div class="tabbable">
<ul class="nav nav-tabs">
<li ng-class="{active: activeTab == 'tab1'}"><a ng-click="activeTab = 'tab1'" href="">tab1</a></li>
<li ng-class="{active: activeTab == 'tab2'}"><a ng-click="activeTab = 'tab2'" href="">tab2</a></li>
<li ng-class="{active: activeTab == 'tab3'}"><a ng-click="activeTab = 'tab3'" href="">tab3</a></li>
</ul>
</div>
<div class="tab-content">
<div ng-include="'/'+activeTab"></div>
</div>
tab1.html:
<h1>TAB1</h1>
Something
something.html
<h1>SOMETHING</h1>
Now the question is how to change the tab1 content to something.html while the tab1 is active?
As pointed out in other examples there are many ways to do this. Direct DOM manipulation is not really the Angular way of thinking about this kind of use case. A better way to think about it might be:
What possible content can this tab contain?
What will control its' being displayed?
Using the ng-if or ng-switch directive allows you to selectively limit the content based on a variable defined in the scope.
Consider this possibility:
<div class="tabbable">
<ul class="nav nav-tabs">
<li ng-class="{active: activeTab == 'tab1'}"><a ng-click="activeTab = 'tab1'" href="">tab1</a></li>
<li ng-class="{active: activeTab == 'tab2'}"><a ng-click="activeTab = 'tab2'" href="">tab2</a></li>
<li ng-class="{active: activeTab == 'tab3'}"><a ng-click="activeTab = 'tab3'" href="">tab3</a></li>
</ul>
</div>
Based on your code for the included file you could do this:
<div class="tab-content">
<div ng-if="content==='A'" ng-include="'/'+activeTabA"></div>
<div ng-if="content==='B'" ng-include="'/'+activeTabB"></div>
</div>
Another approach is to utilize ng-view and routing. It is more complicated than conditionally including but less complicated than writing a whole new directive.
In short, you define a container element with the ng-view attribute like this
<div ng-view></div>
Then set up a routing table in your javascript code like this
$routeProvider.when('/tab1', {templateUrl: 'partials/tab1.html', controller: 'tab1Controller'});
$routeProvider.when('/tab2', {templateUrl: 'partials/tab2.html', controller: 'tab2Controller'});
For more detail see this link: http://docs.angularjs.org/api/ngRoute.directive:ngView
This is actually a fairly common AngularJS issue, and is handled pretty nicely by nested directives. It works well enough that it's actually one of the demos in the Custom Directive Guide on the AngularJS docs page. It's the last example, "Creating Directives that Communicate". You can see the full code there, but the idea is that you create a 'container' directive for the tab group and a 'pane' directive for the inside content. Your HTML ends up looking like this:
<my-tabs>
<my-pane title="Hello">
<h5 id="pane1">Hello</h5>
<p>This content is in the first pane.</p>
</my-pane>
<my-pane title="World">
<h5 id="pane2">World</h5>
<em>This content is in the second pane.</em>
</my-pane>
</my-tabs>
As #musically_ut pointed out in his comment, there are a lot of ways to handle this. This is just one way, but I think it works out pretty well.

Resources