Create an unordered list n-levels deep using AngularJS - angularjs

While going through some AngularJS examples, I see how easy it is to repeat and create structures. However, I couldn't figure out how to do the following.
Assume we have a json structure like
{
"Asia": {
"India": {
"Bangalore": {},
"Mumbai": {},
"New Delhi": {}
},
"China": {
"Beijing": {},
"Shanghai": {}
}
},
"Europe": {
"France": {
"Paris": {}
},
"Germany": {
"Berlin": {}
}
}
}
What I want to do is - Convert this JSON structure to an Unordered list - The depth of this kind of structure is not known, and can possibly go deeper. How do I perform repeats dynamically using Angular JS?

Your JSON is poorly structured, you're using property names to carry data.
What you really want is something like this:
$scope.continents = [
{
name: 'Asia',
countries: [
{
name: 'India',
cities: [
'Bangalore',
'Mumbai',
'New Delhi'
]
},
{
name: 'China',
cities: [
'Beijing',
'Shanghai'
]
},
]
},
{
name: 'Europe',
countries: [
{
name: 'France',
cities: [
'Peris'
]
},
{
name: 'Germany',
cities: [
'Berlin'
]
},
]
}
];
That said... what it sounds like you're looking to do is create a recursive tree directive of some sort. That gets a little tricky. You'll need to normalize your structure a bit so you can recursively examine it. Then you'll have to create two directives. One for a list, and one for an item:
Here is an example of what I mean...
function Item(name, items) {
this.name = name;
this.items = items || [];
}
app.controller('MainCtrl', function($scope) {
$scope.items = [
new Item('test'),
new Item('foo', [
new Item('foo-1'),
new Item('foo-2', [
new Item('foo-2-1'),
new Item('foo-2-2')
])
]),
new Item('whatever')
];
});
app.directive('tree', function() {
return {
template: '<ul><tree-node ng-repeat="item in items"></tree-node></ul>',
restrict: 'E',
replace: true,
scope: {
items: '=items'
}
};
});
app.directive('treeNode', function($compile) {
return {
restrict: 'E',
template: '<li>{{item.name}}</li>',
link: function(scope, elm, attrs) {
//MAGIC HERE!!!: this will do the work of inserting the next set of nodes.
if (scope.item.items.length > 0) {
var children = $compile('<tree items="item.items"></tree>')(scope);
elm.append(children);
}
}
};
});

In case anyone is interested in the "least-effort" way to do this without creating a directive (not that you shouldn't, but just offering a variation), here is a simple example:
http://jsbin.com/hokupe/1/edit
Also here's a blog post and a 10-15 minutes video on how it works:
http://gurustop.net/blog/2014/07/15/angularjs-using-templates-ng-include-create-infinite-tree/
Sample Code:
<script type="text/ng-template" id="treeLevel.html">
<ul>
<li ng-repeat="item in items">
<input type="checkbox"
name="itemSelection"
ng-model="item._Selected" />
{{item.text}}
<div ng-include=" 'treeLevel.html'"
onload="items = item.children">
</div>
</li>
</ul>
</script>
<div ng-include=" 'treeLevel.html' "
onload="items = sourceItems">
</div>

Related

Sort subarrays in array with AngularJS

If I have a dataset that contains an array of objects, with each object having an array inside of them, how can I sort the subarrays by their properties? For example, here is some sample music data, it has 2 albums with tracks within them:
albums: [
{
type: "album",
name: "Brothers",
tracks: [
{
type: "track",
name: "Everlasting Light"
},
{
type: "track",
name: "Next Girl"
},
{
type: "track",
name: "Tighten Up"
}
]
},
{
type: "album",
name: "Listen",
tracks: [
{
type: "track",
name: "Around Town"
},
{
type: "track",
name: "Forgive & Forget"
}
]
}
]
The result would look like this:
- Around Town
- Everlasting Light
- Forgive & Forget
- Next Girl
- Tighten Up
Is there any way I can use an ng-repeat to create an alphabetically sorted list of music tracks?
I'd imagine it working something like below, but I tried with no success.
<p ng-repeat="track in albums.tracks">{{track.name}}</p>
Since you need only the tracks of albums, you should merge all the tracks in a single array and then just sort it alphabetically. Here's is a snippet working:
var app = angular.module('app', []);
app.controller('mainCtrl', function($scope) {
$scope.albums = [
{
"type":"album",
"name":"Brothers",
"tracks":[
{
"type":"track",
"name":"Everlasting Light"
},
{
"type":"track",
"name":"Next Girl"
},
{
"type":"track",
"name":"Tighten Up"
}
]
},
{
"type":"album",
"name":"Listen",
"tracks":[
{
"type":"track",
"name":"Around Town"
},
{
"type":"track",
"name":"Forgive & Forget"
}
]
}
];
$scope.tracks = [];
angular.forEach($scope.albums, function(value) {
$scope.tracks = $scope.tracks.concat(value.tracks);
});
});
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.min.js">
</script>
</head>
<body ng-controller="mainCtrl">
<div ng-repeat="track in tracks | orderBy:'name'">
<span ng-bind="track.name"></span>
</div>
</body>
</html>
You can use the orderby filter to sort your list based on a property. The angular website has more info on this https://docs.angularjs.org/api/ng/filter/orderBy
Here is a plunker that might help, https://plnkr.co/edit/goxNA9PvBeFL0hyWp9hR?p=preview
You can use the filter directly in your html to sort by a property.
<div ng-repeat="album in albums">
<h2>{{album.name}}</h2>
<div ng-repeat="track in album.tracks | orderBy:'name'">
{{track.name}}
</div>
</div>

Why it not working? following angularjs Example

Here i created some Example.. In this Example 1 I put directly
//{{arr.Description}}
like this its working but via directive its not working what misstake i did
can please explain me?
Thanks friends
var app = angular.module('components', []);
app.directive('subpane', function() {
return {
restrict: 'E',
scope: {
array: '#'
},
template:'<li ng-repeat="arr in array">{{arr.Description}}</li>',
link: function(scope) {
}
};
})
app.controller('tabController', ['$scope', function ($scope) {
$scope.array =[{
"title": 0,
"Description": "Select your option"
},
{
"title": 1,
"Description": "Male"
},
{
"title": 2,
"Description": "Female"
},
{
"title": 3,
"Description": "Unknown"
}];
}])
<script data-require="angular.js#~1.3.15" data-semver="1.3.15" src="https://code.angularjs.org/1.3.15/angular.js"></script>
<body ng-app="components" ng-controller="tabController">
<span>Example 1 Working fine</span>
<li ng-repeat="arr in array">{{arr.Description}}</li>
<span>Example 2 Not Working.. Here also same thing only i did via directive.. why this is not working?</span>
<subpane array={{array}}></subpane>
</body>
Use '=' for the scope param and remove '{{}}' in the markup.
var app = angular.module('components', []);
app.directive('subpane', function() {
return {
restrict: 'E',
scope: {
array: '='
},
template:'<li ng-repeat="arr in array">{{arr.Description}}</li>',
link: function(scope) {
}
};
})
app.controller('tabController', ['$scope', function ($scope) {
$scope.array =[{
"title": 0,
"Description": "Select your option"
},
{
"title": 1,
"Description": "Male"
},
{
"title": 2,
"Description": "Female"
},
{
"title": 3,
"Description": "Unknown"
}];
}])
<script data-require="angular.js#~1.3.15" data-semver="1.3.15" src="https://code.angularjs.org/1.3.15/angular.js"></script>
<body ng-app="components" ng-controller="tabController">
<span>Example 1 Working fine</span>
<li ng-repeat="arr in array">{{arr.Description}}</li>
<span>Example 2 Not Working.. Here also same thing only i did via directive.. why this is not working?</span>
<subpane array=array></subpane>
</body>
You need to add "plain: true" in the directive if you don't use templateUrl
app.directive('subpane', function() {
return {
restrict: 'E',
scope: {
array: '=' // = not #, = is for object/array ; = is for text/number.
},
template:'<li ng-repeat="arr in array">{{arr.Description}}</li>',
plain: true,
link: function(scope) {
}
};
})
In the html dont put {{ }} in the parameter array
<subpane array="array"></subpane>
camden_kid has posted an acceptable answer but not probably the best one. In your template:
<subpane array={{array}}></subpane>
Must have the quotes:
<subpane array="{{array}}"></subpane>
Please also note that camden_kid suggested to change
scope: {
array: '#'
}
To
scope: {
array: '='
}
This is what is needed for double binding and it is only necessary if you have to edit in any way the original value (in the parent scope) from within the directive's isolated scope.
You probably should read more carefully the docs: https://docs.angularjs.org/guide/directive

Angular select ngChange get the parent json object key not the value

I've created a hypothetical example; since i can't share my real example with you all. So forgive my hasty json file i created.
To the problem. Say i have a select populated like so using a json file which contains an array of (US State) objects:
State.json
{ "states":
[
{
code: "AL",
name: "Alabama"
},
{
code: "AK",
name: "Alaska"
},
{
code: "AS",
name: "American Samoa"
},
{
code: "AZ",
name: "Arizona"
},
{
code: "AR",
name: "Arkansas"
},
{
code: "CA",
name: "California"
},
{
code: "CO",
name: "Colorado"
},
{
code: "CT",
name: "Connecticut"
},
... etc...
]}
I pull in the json file and set it to a scope item like so:
main-controller.js
app.controller('MainCtrl', function ('$scope') {
$scope.states = [
{ code: "AL": name: "Alabama" },
//etc
];
$scope.selectStateChange = function (stateCode) {
console.log(stateCode);
}
});
index.html
Here's my select:
<select ng-model="selectedState" ng-change="selectStateChange(selectedState)">
<option ng-repeat="state in states">{{state.name}}</option>
</select>
My Problem
How does one get the actual state code to be passed into function selectStateChange on my ng-change?
You should try using ng-options instead of a ng-repeat on options.
This way your model will be up to date and it will be quite convenient to access the selected object.
It should looks like this in your case :
<select ng-model="selectedState" ng-options="state.name for state in states" ng-change="selectStateChange()">
</select>
and your JS should display your object:
app.controller('MainCtrl', function ('$scope') {
$scope.states = { "AL": "Alabama", //etc }
$scope.selectedState = null;
$scope.selectStateChange = function () {
console.log(selectedState);
}
});
This way, selectedState is equal to {
code: "AL",
name: "Alabama"
}
What get's logged into the console by console.log(stateCode);?
Did you try
$scope.selectStateChange = function (selectedState) {
console.log(selectedState.code);
}

AngularJS : Recursive tree style

as the name suggests i am trying to build a recursive tree directive I has to mimic the structure from the expression builder as that's how its been setup and i want to replace it with an angular component. I have had a look at the other posts on the forum but mine is not exactly the same.
the fiddle thanks to julien for showing me how to fix it.
the model which is based off the expression builder has a structure like a condition object inside a field then an expression array and then another array which will be called nested expression but essentially will contain a condition object and this can be nested as deep as it wants to go.
Anyway so i created a directive to create a list entry for each entry in the expression builder (ng-repeat) then i created a link function to find out when its at the last loop and after that repeat the loop for every item in the nested expression array (there must be a better way to do this).
It works for the nestings but now i want to assign a button at each root level ul so that you add a new entry(expression) to the array another but when i try it seems to duplicate the array and then attach the new value.
The other tasks is to:
And a root level button so that you can add a second nested expression array (i,e, two ul's at the same level) , a root level delete so that you can delete a whole ul nesting and a delete at each li so that you can delete just one expression.
I will keep at the fiddle but maybe someone can solve it before i can get the fiddle working.
Any help will be greatly appreciated
Regards
angular.module("expressionBuilderApp", [])
.controller('ExpressionBuilderController', ['$scope', function($scope) {
$scope.conditions = {
operator : "and",
expressions : [
{
name : "moo"
},
{
name : "mooest"
},
{
name : "mooster"
}
],
nestedexpressions : [
{
operator : "or",
expressions : [
{
name : "bow wow"
},
{
name : "woof woof"
}
],
nestedexpressions : [
{
operator : "or",
expressions : [
{
name : "meow"
},
{
name : "meeew"
}
],
nestedexpressions : [
{
operator : "And",
expressions : [
{
name : "oink"
},
{
name : "squeel"
}
],
nestedexpressions :
[
]
},
{
operator : "Or",
expressions : [
{
name : "hoot hoot"
},
{
name : "stares with yellow eyes"
}
],
nestedexpressions :
[
]
}
]
}
]
}
]
};
}])
.directive('expressionBuilder', function(){
return {
restrict: 'E',
template : '<ul><button ng-click="addNewCondition(conditions.expressions)">add</button><li nested-expressions="" conditions="conditions.nestedexpressions" ng-repeat="expression in conditions.expressions" >{{expression.name}}</li></ul>',
replace: true,
scope: {conditions: "="},
controller: function($scope){
$scope.addNewCondition = function(arrayToPushInto){
var newCondition = {name: "testing"};
arrayToPushInto.push(newCondition);
}
}
}
})
.directive('nestedExpressions', function($compile) {
return {
restrict: 'A',
replace: true,
link: function(scope, element, attrs){
if (scope.$last){
var conditions = scope.conditions;
console.log(conditions) ;
if(conditions.nestedexpressions.length > 0){
element.after($compile('<li ng-repeat="nestedexpression in conditions.nestedexpressions"><expression-builder conditions="nestedexpression"></expression-builder></li>')(scope));
}
}
}
}
});
I don't know why to be honest but i based my code of this so thanks buddy whoever you are.
Ng-include seems to just use the scope of the array passed in and does not duplicate the functions through directive nesting see the [fiddle] (http://jsfiddle.net/cerebral86/g62KP/5/)
Hopefully this helps someone else.
<script type="text/ng-template" id="conditionExpressionBuilder.html">
<div>
<div>
<button ng-click="addNestedCondition(conditions.nestedexpressions)">Add ()</button>
<button ng-click="addNewCondition(conditions.expressions)">Add node</button>
</div>
<div>
<select>
<option ng-selected="{{conditions.operator == operator.value}}" ng-repeat="operator in operators" value="{{operator.value}}">{{operator.display}}</option>
</select>
</div>
<div>
<ul>
<li ng-repeat="expression in conditions.expressions">
<div>
<span>
<button ng-click="delete(expression, conditions.expressions)" ng-disabled="$first">Delete Item</button>
</span>
<span>
<select>
<option ng-selected="{{filterfield.value == expression.filterField}}" ng-repeat="filterfield in filterfields" value="{{filterfield.value}}">{{filterfield.display}}</option>
</select>
</span>
<span>
<select>
<option ng-selected="{{comparator.value == expression.comparator}}" ng-repeat="comparator in comparators" value="{{comparator.value}}">{{comparator.display}}</option>
</select>
</span>
<span><input type="text" value="{{expression.expressionValue}}" /></span>
</div>
</li>
<li ng-repeat="conditions in conditions.nestedexpressions" ng-if="conditions.expressions.length > 0">
<button ng-click="removeNestedCondition($index, $parent.$parent.conditions.nestedexpressions)">Remove ()</button>
<ng-include src="'conditionExpressionBuilder.html'"></ng-include>
</li>
</ul>
</div>
</div>
</script>
angular.module("expressionBuilderApp", [])
.controller('expressionBuilderController', ['$scope', function($scope) {
$scope.operators = [
{
display: "And",
value: "and"
},
{
display: "Or",
value: "or"
}
];
$scope.filterFields = [
{
display: "Code",
value: "code"
},
{
display: "Capital",
value: "capital"
},
{
display: "Government",
value: "government"
},
{
display: "Population",
value: "population"
}
];
$scope.comparators = [
{
display: "contains",
value: "contains"
},
{
display: "Starts With",
value: "startswith"
},
{
display: "Ends With",
value: "endswith"
},
{
display: "Doest not Contain",
value: "doesnotcontain"
}
];
$scope.conditions = {
operator: "and",
expressions: [
{
filterField: "code",
comparator: "contains",
expressionValue: "moo"
},
{
filterField: "capital",
comparator: "startswith",
expressionValue: "mooest"
},
{
filterField: "government",
comparator: "endswith",
expressionValue: "mooster"
}
],
nestedexpressions: [
{
operator: "or",
expressions: [
{
filterField: "code",
comparator: "endswith",
expressionValue: "bow wow"
},
{
filterField: "government",
comparator: "doesnotcontain",
expressionValue: "woof woof"
}
],
nestedexpressions: [
{
operator: "or",
expressions: [
{
filterField: "code",
comparator: "contains",
expressionValue: "meow"
},
{
filterField: "code",
comparator: "contains",
expressionValue: "meeew"
}
],
nestedexpressions: [
{
operator: "And",
expressions: [
{
filterField: "government",
comparator: "doesnotcontain",
expressionValue: "oink"
},
{
filterField: "code",
comparator: "endswith",
expressionValue: "squeel"
}
],
nestedexpressions:
[
]
},
{
operator: "Or",
expressions: [
{
filterField: "code",
comparator: "doesnotcontain",
expressionValue: "hoot hoot"
},
{
filterField: "code",
comparator: "contains",
expressionValue: "stares with yellow eyes"
}
],
nestedexpressions:
[
]
}
]
}
]
}
]
};
}])
.directive('conditionExpressionBuilder', function () {
return {
restrict: 'E',
replace: true,
templateUrl: 'conditionExpressionBuilder.html',
scope: {
conditions: "=",
operators: "=",
filterfields: "=",
comparators: "="
},
controller: function($scope)
{
$scope.addNewCondition = function (arrayToPushInto) {
var newCondition = { name: "testing" };
arrayToPushInto.push(newCondition);
};
$scope.delete = function (expressionItem, expressionArray) {
var index = expressionArray.indexOf(expressionItem);
expressionArray.splice(index, 1);
};
$scope.removeNestedCondition = function (index, nestedExpressionArray) {
nestedExpressionArray.splice(index, 1);
};
$scope.addNestedCondition = function (nestedExpressionsArray) {
var nestedConidtion = { operator: "", expressions: [{}], nestedexpressions: [{}] };
nestedExpressionsArray.push(nestedConidtion);
};
}
}
});

Angular JS $scope $resource & Directive - Directive Loading faster than scope thus cant see data

I am using an API to load (Data) to my $scope resource, and I took an example from a directive online to create a treeview. Recursive Tree View Example
However I am changing a few things to load data from an API. Please note the commented data... when I uncomment my data everything works great, however when I use $scope.treeFamily = TreeView.query() I think there is a delay between the directive executing and me getting no data. Any insight will be helpful. Thank you!
var module = angular.module("module", ["ngResource", "ngRoute"]);
module.factory('TreeView', function ($resource) {
return $resource('/api/TreeView/:Id', {}, {
//show: { method: 'GET', isArray: true }, //<--- need to do query instead of show....
query: { method: 'GET', isArray: false},
update: { method: 'PUT', params: { id: '#id' } },
delete: { method: 'DELETE', params: { id: '#id' } }
})
});
module.controller('TreeCtrl', function ($scope, TreeView) {
$scope.treeFamily = TreeView.query();
//$scope.treeFamily = {
// name: "Parent",
// children: [{
// name: "Child1",
// children: [{
// name: "Grandchild1",
// children: []
// }, {
// name: "Grandchild2",
// children: []
// }, {
// name: "Grandchild3",
// children: []
// }]
// }, {
// name: "Child2",
// children: []
// }]
//};
});
module.factory('RecursionHelper', ['$compile', function ($compile) {
var RecursionHelper = {
compile: function (element) {
var contents = element.contents().remove();
var compiledContents;
return function (scope, element) {
if (!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function (clone) {
element.append(clone);
});
};
}
};
return RecursionHelper;
}]);
module.directive("tree", function (RecursionHelper) {
return {
restrict: "E",
scope: { family: '=' },
template:
'<p>{{ family.name }}</p>' +
'<ul>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child"></tree>' +
'</li>' +
'</ul>',
compile: function (element) {
return RecursionHelper.compile(element);
}
};
});
The Result from what i get there using the following HTML.
<!DOCTYPE html>
<html lang="en" ng-app="module">
<head>
<title></title>
</head>
<body>
<div class="container">
<div ng-controller="TreeCtrl">
<table>
<tr>
<th>Name</th>
</tr>
<tr ng-repeat="result in treeFamily">
<td> From Table: {{result.name}}</td>
</tr>
</table>
<tree family="treeFamily"></tree>
</div>
<div ng-view=""></div>
</div>
Result :
Name
From Table: Parent
HOWEVER, this is from the the ng-repeat within my table, so i know the API is sending DATA and it is readable.
{
ID: "1",
type: "Folder",
name: "Parent",
children: []
}
The problem is that it seems that the directive is not loading this data.... If however uncomment the built in data I have for that scope it works fine...
I have a feeling that my directive is loading faster than my API call so I get no data. Am i doing something wrong?
Any help will be appreciated!
Additional Research...
$scope.treeFamily = { "ID": "1", "type": "Folder", "name": "Harvest", "children": null };
$scope.treeFamily = [{ "ID": "1", "type": "Folder", "name": "Harvest", "children": null }];
This is the difference.....
If i try to do ng-repeat on
$scope.treeFamily = { "ID": "1", "type": "Folder", "name": "Harvest", "children": null };
It will not work because it is expecting an object [...]
$scope.treeFamily = [{ "ID": "1", "type": "Folder", "name": "Harvest", "children": null }];
Thus the above will work.
However, when using the recursive tree, it seems as though it does not EXPECT to see an object other than children... thus
$scope.treeFamily = [{ "ID": "1", "type": "Folder", "name": "Harvest", "children": null }];
will fail......
HOWEVER, I changed my API to return like this:
{ "ID": "1", "type": "Folder", "name": "Harvest", "children": null }
It still wont work!!!!!
This is probably an Angular version issue. Automatic promise unwrapping was removed in version 1.2. Change the code to:
var treeFamily = TreeView.query(function(){
$scope.treeFamily = treeFamily;
});
or use the more explicit promise syntax:
TreeView.query().$promise.then(function(treeFamily){
$scope.treeFamily = treeFamily;
});
I don't think the order matters. Since in the documentation of $resource it says:
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data. This means that in most cases one never has to write a callback function for the action methods.
Are you sure data is returned from the server?

Resources