I have the following data structure declared in my controller:
$scope.tree = {
label:"A",
parent:{
label:"B",
parent:{
label:"C"
}
}
};
What i would like to end up with is:
<ul>
<li>C
<ul>
<li>B
<ul>
<li>A</li>
</ul>
</li>
</ul>
</li>
<ul>
I've tried various ways of doing this including external templates, custom directives etc and I just can't seem to get it to work.
Any thoughts?
In the other answer that you linked to inside the comments, we use the ng-repeat directive to create a new scope for the template.
Perhaps, you could mimic this behavior with your data by wrapping your parent property inside an array [...]:
controller
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.tree = {
label:"A",
parent:{
label:"B",
parent:{
label:"C"
}
}
};
});
html
<ul ng-repeat="field in [tree]" ng-include="'tree.html'"></ul>
template
<li>
<div>{{field.label}} {{[field.parent]}}</div>
<ul ng-if="field" ng-repeat="field in [field.parent]" ng-include="'tree.html'"></ul>
</li>
Here is the link to the plunker: http://plnkr.co/edit/7shibX0leK1TXVl5kfPc?p=preview
A better solution would be to create your own ng-include and pass your child object to it.
Related
how to write a service or factory for bootstrap tabs using angularJs.
I placed in one controller but need a common function(repeated code) for different controllers.
<ul class="nav nav-tabs">
<li data-ng-class="{active:tab===0}">
<a href ng-click="changeTab(0)"> <i class="fa fa-list"></i></a>
</li>
<li data-ng-class="{active:tab===1}">
<a href ng-click="changeTab(1)"><i class="fa fa-user"></i></a>
</li>
</ul>
<div data-ng-class="{activeTab:tab===0}" ng-show="isActiveTab(0)">
tab1
</div>
<div data-ng-class="{activeTab:tab===1}" ng-show="isActiveTab(1)">
tab2
</div>
controller
$scope.tab = 0;
$scope.changeTab = function(newTab){
$scope.tab = newTab;
};
$scope.isActiveTab = function(tab){
return $scope.tab === tab;
};
Managing tabs is a view concern. Rather than implementing a factory, I recommend creating two directives: tabContainer and tab. The tab directive registers itself with the parent tabContainer using the require attribute to access the parent directive's controller API.
Demo
Usage
<tab-container selected="tab2">
<tab name="tab1">tab1</tab>
<tab name="tab2">tab2</tab>
</tab-container>
Parent Directive
The parent directive publishes the following controller API that the child tab directives will access:
tabContainer controller
// registers this tab with the parent tabContainer
this.register = function(element) {
$scope.tabs.push(element);
}
// returns the selected tab object whose
// name property indicates which tab is active
this.getSelected = function() {
return $scope.selectedTab;
}
Child Directive
The tab directive is able to access the parent controller by requiring the parent directive in its directive definition, and accessing the parent directive's controller as the 4th argument to the tab directive's link function:
tab directive definition
scope: true,
require: '^tabContainer',
link: function(scope, element, attr, tabContainer) {
// set the tab so that it is visible in the tab directive's scope.
scope.tab = { name: attr.name, element:element};
scope.selectedTab = tabContainer.getSelected();
tabContainer.register(scope.tab);
}
The scope is set to true so that each tab will create its own child scope and not interfere with the scope of other tabs.
Template Files
For example purposes, the directive templates are embedded in the HTML:
<script type="text/ng-template" id="tabContainer.html">
<ul class="nav nav-tabs">
<li ng-repeat="tab in tabs" data-ng-class="{active:selectedTab.name===tab.name}">
<a href ng-click="changeTab(tab)"> <i class="fa fa-list">{{tab.name}}</i></a>
</li>
</ul>
<ng-transclude></ng-transclude>
</script>
<script type="text/ng-template" id="tab.html">
<div data-ng-class="{activeTab:selectedTab.name===tab.name}" ng-show="selectedTab.name === tab.name">
<ng-transclude></ng-transclude>
</div>
</script>
It is recommended to move these to dedicated HTML files.
Changing the Active Tab
The user is able to change the active tab by clicking the tab link. This is achieved by publishing a $scope function in the parent controller:
$scope.changeTab = function(tab) {
$scope.selectedTab.name = tab.name;
}
Creating a Tabs Module
The beauty of AngularJS and its pluggable modular architecture is that you can extend the AngularJS directive ecosystem, and have the directives work together seamlessly. For example, you could encapsulate the above tabs directive into a tabs module, and even use the ngRepeat directive to bind the tabs.
Demo
Controller
var app = angular.module('app',['tabs']);
app.controller('ctrl', function($scope) {
$scope.tabData = [
{ name: 'tab1', body: 'You selected tab1!'},
{ name: 'tab2', body: 'You selected tab2!'},
{ name: 'tab3', body: 'You selected tab3!'},
{ name: 'tab4', body: 'You selected tab4!'},
];
});
View
<div class="container" ng-controller="ctrl">
<tab-container selected="tab1">
<tab ng-repeat="tab in tabData" name="{{tab.name}}">{{ tab.body }} </tab>
</tab-container>
</div>
Hi I write it with out using any service or factory
see the example
<ul ng-init="tab = 1">
<li ng-class="{active:tab===1}">
<a href ng-click="tab = 1">Tab1</a>
</li>
<li ng-class="{active:tab===2}">
<a href ng-click="tab = 2">Tab2</a>
</li>
<li ng-class="{active:tab===3}">
<a href ng-click="tab = 3">Tab3</a>
</li>
<br><br>
<p ng-show="tab === 1"> Tab1 content </p>
<p ng-show="tab === 2"> Tab2 content</p>
<p ng-show="tab === 3"> Tab3 content</p>
</ul>
Dynamically change it through controller see the working example here
The structure for controllers/services in Angular are explained well here:
https://github.com/johnpapa/angular-styleguide
In app.js, we declare an angular application, give it a name, and any dependencies (ng-route / ng-grid). $http calls should be made using a factory or a service, and the controller should call the service to fetch or post data. From the angular documentation, "services are substitutable objects that are wired together using dependency injection (DI). You can use services to organize and share code across your app."
https://docs.angularjs.org/guide/services
app.js:
var app = angular.module('appname', []);
app.factory('httpResponseErrorInterceptor'...
app.config(['$httpProvider',...
Controller:
angular.module('appname').controller("NameCtrl", ["$scope", "$log", "$window", "$http", "$timeout", "SomeService",
function ($scope, $log, $window, $http, $timeout, TabService) {
//your controller code
$scope.tab = 0;
$scope.changeTab = function(newTab){
$scope.tab = newTab;
};
$scope.isActiveTab = function(tab){
return $scope.tab === tab;
};
}
]);
Service:
angular.module('appname').service("TabService", function () {
//Your code for shared functionality regarding tab service
var currentTab = {};
return {
currentTab : currentTab
}
});
I have a simple list item being parsed with ng-repeat:
<ul>
<li ng-repeat="item in items" class="commonClass" ng-class="{'on': on_var}" ng-click="on_var=!on_var">
{{item.name}}
<li>
</ul>
clicking on a list-item will add the class name 'on' as expected. but I want to remove all other 'on' classes as soon as you click on another list-item and only add it to the one clicked. I come from a jQuery background and I am new to angular. All I want to do is something like this:
$("li.commanClass").click(function(){
$("li.commonClass").removeClass('on');
$(this).addClass('on');
})
I want to know what is the "angular way" of achieving this result
jsfiddle
Thanks.
In angular you want to pass object references to child scopes as much as possible. This way you can use prototypical object inheritance that can branch down many levels.
Think of a scope as being a model. In the case of ng-repeat it creates a child scope for each element. So if the parent scope holds a model it will be passed as reference to the child scopes. Each of these child scopes will hold a reference to the parent scope model object.
Thus you can do:
<li ng-repeat="item in model.items"
ng-class="{'on': model.selected==item}"
ng-click="model.selected=item">{{ item.name }}</li>
Then in controller:
$scope.model = {
selected: null,
items = [
{name: "Apple"},
{name: "Banana"},
{name: "California"}
]
};
Try to avoid using functions as above answer does. These functions will get called many times and will add unnecessary extra overhead.
Valuable post to read: "Thinking in AngularJS" if I have a jQuery background?
DEMO
You can add a variable to your scope to maintain which item is selected, and a function on your scope that toggles the variable.
Controller:
app.controller('myCtrl', function($scope) {
$scope.items =
[
{name: "Apple"},
{name: "Banana"},
{name: "California"}
]
$scope.selectItem = function( item ) {
$scope.selectedItem = item;
};
})
HTML:
<div ng-app="myApp">
<ul ng-controller="myCtrl">
<li ng-repeat="item in items" class="commonClass" ng-class="{'on': selectedItem === item}" ng-click="selectItem(item)">
{{ item.name }}
</li>
</ul>
</div>
Fiddle coming at ya of jsparks answer:
http://jsfiddle.net/eHDTF/
See fiddle for code!
I think you should refer a directive for whole your project instead of controller.
<div class="nav-item">
<ul>
<li active-me="on">Item 1</li>
<li active-me="on">Item 2</li>
<li active-me="on">Item 3</li>
<li active-me="on">Item 4</li>
<li active-me="on">Item 5</li>
</ul>
</div>
Then create activeMe directive:
angular.module('app').directive('activeMe', function(){
return {
restrict: 'A',
scope: {
activeMe: '#'
},
link: function(scope, element, attrs) {
element.bind('click', function() {
if (scope.activeMe !== undefined && scope.activeMe.trim().length > 0) {
element.parent().children().removeClass(scope.activeMe);
element.addClass(scope.activeMe);
} else {
element.parent().children().removeClass('active');
element.addClass('active');
}
});
}
};
});
How can i loop html element in angularJS?
html:
<ul id="list">
<li>AngularJS</li>
<li>jQuery</li>
<li>Backbone</li>
<li>ExtJS</li>
</ul>
In jQuery:
var array = [];
$('ul#list li').each(function(index,value){
array.push($(value).text());
});
How can i get the same result in angularJS?
Try this.
<ul ng-init="list = ['AngularJS','jQuery',Backbone','ExtJS']" ng-repeat="item in list">
<li>{{item}}</li>
</ul>
In Angular you would tackle this problem the other way around.
Define your array in your controller:
$scope.array = ['AngularJS', 'JQuery', 'Backbone', 'ExtJS'];
and in your html use the ng-repeat directive:
<ul>
<li ng-repeat="a in array">{{a}}</li>
</ul>
and now you can access the array in JS via $scope.array
What I mean is, say I have this HTML:
<ul ng-controller="ContactsCtrl">
<li ng-repeat="contact in contacts">
<div class="name">{{name}}</div>
<div class="email">{{email}}</div>
</li>
</ul>
This works just fine if I initialize the contacts collection from JavaScript:
app.controller('ContactsCtrl', ['$scope', function($scope) {
$scope.contacts = [
{ name: 'Dan', email: 'dan#example.com' },
{ name: 'Bob', email: 'bob#example.com' }
];
}]);
What if, instead, I want to initialize the contents of the collection from the HTML side? Something like:
<ul ng-controller="ContactsCtrl" ng-model="contacts">
<li>
<div class="name" ng-bind="name">Dan</div>
<div class="email" ng-bind="email">dan#example.com</div>
</li>
<li>
<div class="name" ng-bind="name">Bob</div>
<div class="email" ng-bind="email">bob#example.com</div>
</li>
</ul>
And then from JavaScript I would have $scope.contacts initialized based on what I have in the HTML.
Is this possible?
you can do a ng-init="" in your html to initialize a variable or sets of variables but that doesn't seem to be quite what your asking. Why would you want your second example? What are you trying to do with it?
Rather use jQuery and parse HTML DOM, if You have at least... 500 records, other way is 'copy and paste' - it will be faster.
Here is short info 'how it works'
Tutorial
Controllers:
var ProductsCtrl = function ($scope) {
$scope.products = [ {name: 'one'}, {name: 'two'}, {name:'three'} ];
};
var ProductCtrl = function ($scope) {
$scope. // How do I access the current product name?
};
view:
<ul ng-controller='ProductsCtrl'>
<li ng-repeat='product in products' ng-controller='ProductCtrl'>
</li>
</ul>
The current product is accessible in the scope as "product" in your case.
if you repeat something like this :
ng-repeat="item in items" your child controller will have "item" in the scope.
So in your example :
<ul ng-controller='ProductsCtrl'>
<li ng-repeat='product in products' ng-controller='ProductCtrl'>
{{product.name}}
</li>
</ul>
#ganaraj already provided the correct answer.
But I'd like to point out that in your example, you probably don't need to declare a controller in the <li> element. The child scopes created by ng-repeat for each product have access to the ProductCtrl controller. You should only need
<li ng-repeat='product in products'>
See also section "Controller Inheritance Example" on the Understanding the Controller Component page.
You can add methods to ProdctsCtrl's $scope that take a product argument, which could then be called inside the ng-repeat. E.g., in your controller:
var ProductsCtrl = function ($scope) {
...
$scope.totalPrice = function(product) {
return product.price * product.quantity;
}
}
In your HTML:
<li ng-repeat='product in products'>
total cost = {{totalPrice(product)}}
</li>