Looping through deep objects in ng-repeat - javascript

I'm in angular and i have a object like this.
var items = [{
title: 'Something',
children: [
{ title: 'Hello World' },
{ title: 'Hello Overflow' },
{ title: 'John Doe', children: [
{ title: 'Amazing title' },
{ title: 'Google it' },
{ title: 'I'm a child', children: [
{ title: 'Another ' },
{ title: 'He\'s my brother' },
{ title: 'She\'s my mother.', children: [
{title: 'You never know if I'm going to have children'}
]}
]}
]}
]
}];
I wan't to loop through all of these so i have something like this.
    • Something
       • Hello World
       • Hello Overflow
       • John Doe
          • Amazing Title
          • Google it
          • I'm a child
              • Another
              • He's my brother
              • She's my mother
                  • You never know if I'm going to have children
The problem is I wouldn't know how deep this object will go or what's in it. so I wouldn't be able to do it manually. I have done a basic loop with ng-repeat in the fiddle provided at the bottom, but i can't figure out how I can automatically loop through these and create nested <ul>'s and <li>'s.
What would be the best way to accomplish this?
Demo: http://jsfiddle.net/XtgLM/

You don't need to make a custom directive, what you want is to use an inline template that calls it's self.
I forked your fiddle.
http://jsfiddle.net/MasterMorality/E99Gh/2/
basically it looks like this:
<script type='text/ng-template' id="item.html">
...
<div ng-repeat="x in x.childrens" ng-include="'item.html'"></div>
</script>
...
<div ng-repeat="x in things" ng-include="'item.html'"></div>
I should note that you are not actually overwriting x, since angular creates a new scope for each repeated item.

Here you go:
html
<div ng-app="app" ng-controller="test">
<ul>
<li nested-item ng-repeat="item in items">{{item.title}}</li>
</ul>
</div>
JavaScript
var items = [{
title: 'Something',
children: [
{ title: 'Hello World' },
{ title: 'Hello Overflow' },
{ title: 'John Doe', children: [
{ title: 'Amazing title' },
{ title: 'Google it' },
{ title: 'Im a child', children: [
{ title: 'Another ' },
{ title: 'He\'s my brother' },
{ title: 'She\'s my mother.', children: [
{title: 'You never know if im going to have children'}
]}
]}
]}
]
}];
var app = angular.module('app', []);
app.controller('test', function( $scope ) {
$scope.items = items;
});
app.directive('nestedItem', ['$compile', function($compile){
return {
restrict: 'A',
link: function(scope, element){
console.log(element);
if (scope.item.children){
var html = $compile('<ul><li nested-item ng-repeat="item in item.children">{{item.title}}</li></ul>')(scope);
element.append(html);
}
}
};
}]);
I forked your fiddle:
http://jsfiddle.net/c4Kp8/
Actually I must confess that I like Master Morality's approach but you can also go with a custom directive. The key thing to know if you go that route is that you need to intercept on the item level to manually check if the current item has children, and if so, $compile the directive for the node yourself.
UPDATE
However, there is one thing that should bother us in the above code. The duplication of html code (inlined in the directive) is a code smell. If you like, you can get really funky and fix this by introducing a generic template-code directive which doesn't do anything else but providing the code of the node where it is applied on as a template for other directives.
So then our solution would look like this:
html
<div ng-app="app" ng-controller="test">
<ul template-code>
<li nested-item ng-repeat="item in items">{{item.title}}</li>
</ul>
</div>
JavaScript
var items = [{
title: 'Something',
children: [
{ title: 'Hello World' },
{ title: 'Hello Overflow' },
{ title: 'John Doe', children: [
{ title: 'Amazing title' },
{ title: 'Google it' },
{ title: 'Im a child', children: [
{ title: 'Another ' },
{ title: 'He\'s my brother' },
{ title: 'She\'s my mother.', children: [
{title: 'You never know if im going to have children'}
]}
]}
]}
]
}];
var app = angular.module('app', []);
app.controller('test', function( $scope ) {
$scope.items = items;
});
app.directive('templateCode', function(){
return {
restrict: 'A',
controller: function(){},
compile: function(element){
element.removeAttr('template-code');
//ATTENTION: We need to trim() here. Otherwise AngularJS raises an exception
//later when we want to use the templateCode in a $compile function.
//Be aware that we assume a modern browser
//that already ships with a trim function.
//It's easy to secure that with a polyfill.
var templateCode = element.parent().html().trim();
return function(scope, iElement, iAttrs, controller){
controller.templateCode = templateCode;
}
}
}
});
app.directive('nestedItem', ['$compile', function($compile){
return {
restrict: 'A',
require: '^templateCode',
link: function(scope, element, iAttr, controller){
if (scope.item.children){
scope.items = scope.item.children;
var html = $compile(controller.templateCode)(scope);
element.append(html);
}
}
};
}]);
Plunker: http://jsfiddle.net/2rQWf/

You're probably going to need to create your own directive passing in the object to iterate over. Put a watch on the object passed in and when that fires run some recursive function that appends elements to the element that the directive is on.
You can get at the DOM element from the element parameter on the directive link function. You can obviously append DOM elements using that same element parameter.

Related

How can I use ng-repeat to iterate through arrays associated using dynamic keys

I am trying to use ng-repeat to iterate through an array of objects and use each objects ID to look up the data binded to a checklist model.
I have the following javascript object in a project I'm working on:
{
diagnosis: {
mainfractures: [
{
id: "metacarpal",
textinput_id: "metacarpal_text",
title: "5th Metacarpal",
},
{
id: "proximal_phalanx",
textinput_id: "proximal_phalanx_text",
title: "Proximal Phalanx",
},
{
id: "middle_phalanx",
textinput_id: "middle_phalanx_text",
title: "Middle Phalanx",
},
{
id: "distal_phalanx",
textinput_id: "distal_phalanx_text",
title: "Distal Phalanx",
},
{
id: "scaphoid_fracture",
textinput_id: "scaphoid_fracture_text",
title: "Scaphoid Fracture",
}
]
}}
Here is what I have for my checklist model. As the user selects a checkbox, a value is binded to the array associated with that fracture.
$scope.checklists = {
"diagnosis": {
metacarpal: [],
proximal_phalanx: [],
middle_phalanx: [],
distal_phalanx: [],
scaphoid_fracture: []
}
}
Checklist Image Example
Once a users makes a selection similar to the image above the the checklist model for metacarpal should look like this: metacarpal: ["head"]
What I'm trying to do is list each of the users selection in bulletpoint via fracture.id. I'm trying to accomplish it with this piece of code but it's only listed the fracture title so far. is it a problem with trying to interpolate fracture.id into ng-repeat?
<div ng-repeat="fracture in diagnosis.mainfractures">
<div > <!--ng-if="checklists['diagnosis'][fracture.id] > 0"-->
<h4>{{ fracture.title }}</h4>
<div class="row">
<ul type="disc">
<li ng-repeat="selection in checklists['diagnosis'][fracture.id]">
• {{ capitalize(selection) }}
</li>
</ul>
</div>
</div>
</div>
Based on your supplied code, I'd have to say your issue is actually due to JS syntax errors. You're missing commas after each object item and there is a random double quote here scaphoid_fracture"[].
$scope.checklists = {
"diagnosis": {
metacarpal: []
proximal_phalanx: []
middle_phalanx: []
distal_phalanx: []
scaphoid_fracture"[]
}
}
Here is a fully working jsfiddle
Adjusted the code a bit:
capitalize ->
uppercase(https://docs.angularjs.org/api/ng/filter/uppercase)
some syntax errors
But seems that access by dynamic object key inside ng-repeat works pretty correct
https://jsbin.com/rodecosoyo/1/edit?html,js,output
Angular Application
var app = angular.module('arrayid', []);
app.controller('arrayContainerCtrl', function($scope) {
$scope.diagnosis = {
mainfractures: [{
id: "metacarpal",
textinput_id: "metacarpal_text",
title: "5th Metacarpal",
}, {
id: "proximal_phalanx",
textinput_id: "proximal_phalanx_text",
title: "Proximal Phalanx",
}, {
id: "middle_phalanx",
textinput_id: "middle_phalanx_text",
title: "Middle Phalanx",
}, {
id: "distal_phalanx",
textinput_id: "distal_phalanx_text",
title: "Distal Phalanx",
}, {
id: "scaphoid_fracture",
textinput_id: "scaphoid_fracture_text",
title: "Scaphoid Fracture",
}]
};
$scope.checklists = {
"diagnosis": {
metacarpal: ['1', '2'],
proximal_phalanx: ['2', '3'],
middle_phalanx: ['3'],
distal_phalanx: ['4'],
scaphoid_fracture: ['5']
}
};
});
Markup:
<body ng-app='arrayid' ng-controller='arrayContainerCtrl'>
<div ng-repeat="fracture in diagnosis.mainfractures">
<h4>{{ fracture.title }}</h4>
<div class="row">
<ul type="disc">
<li ng-repeat="selection in checklists['diagnosis'][fracture.id]">
• {{ selection | uppercase }}
</li>
</ul>
</div>
</div>
</body>

Angular GroupBy using without ng-repeat

In AngularJS there is the groupBy method for ng-repeats. I'm using that to make a list of times a product with the same title is in the array I'm ng-repeating, and calculate the length and total price of it like this:
<li ng-repeat="(key, value) in products | groupBy: 'title'">
<span>{{value.length}} items</span>
<strong>{{ value[0].title }}</strong>
<tt>{{ (value[0].price*value.length) | currency: '$' }}</tt>
</li>
This works fine, but I wanna re-use that same calculation in the Controller. So I have an object/array like:
{
{
length: 10,
title: 'test 1',
price_total: 200.99,
},
{
length: 3,
title: 'test 2',
price_total: 3.99,
},
}
But I can't figure out how to use the same GroupBy kinda thing.
You can inject AnguarJS filters like this:
angular.module('myApp')
.controller('MyCtrl', function($filter, $scope) {
$scope.myCollection = [{
title: 'Wut',
text: 'Wat'
}, {
title: 'Que',
text: 'paso'
}]
$scope.grouped = $filter('groupBy')($scope.myCollection, 'title');
})
in order to get the same functionality that you would from a directive. Documentation here

Why angular-xeditable doesn't highlight the current value at the first run?

I use angular-xeditable with dropdowns for in-place editing like that (jsFiddle). The strange thing is that I don't see the current value highlighted as selected when I click on xeditable link right after page is loaded. Any ideas on why is this happening?
HTML
Person: <a href="#" editable-select="user.name"
buttons="no"
e-ng-options="p as personDetails.text for (p, personDetails) in people">
{{people[user.name].text}} </a>
Status: <a href="#" editable-select="user.status"
buttons="no"
e-ng-options="s as statusDetails.text for (s, statusDetails) in statuses">
{{statuses[user.status].text}}
</a>
Javascript
app.run(function(editableOptions) {
editableOptions.theme = 'bs3';
});
app.controller('Ctrl', function($scope, $filter) {
$scope.user = {
status: 2,
name: 2
};
$scope.statuses = {
'1': { text: 'status1'},
'2':{ text: 'status2'},
'3':{ text: 'status3'},
'4':{ text: 'status4'}
};
$scope.people = {
'1': { text: 'p1'},
'2':{ text: 'p2'},
'3':{ text: 'p3'},
'4':{ text: 'p4'}
};
});
Not fond of the names of the properties i chose, but I prefer the use of an array, the issue is solvable if you use objects:
<div ng-app="app" ng-controller="Ctrl">
Person: <a href="#"
editable-select="user.person"
buttons="no"
e-ng-options="p.text for p in people track by p.value"> {{user.person.text}} </a>
</div>
app.controller('Ctrl', function($scope, $filter) {
$scope.people = [
{ value: '1', text: 'p1' },
{ value: '2', text: 'p2' },
{ value: '3', text: 'p3' },
{ value: '4', text: 'p4' }
];
$scope.user = {person: $filter('filter')($scope.people, {value : 3})[0]};
});
Also it wont work with the version of AngularJS you were referencing, here is a working fiddle.

angularjs bind template as item inside ngrepeat

I have a special situation in the angularjs, i cant template inside ngRepeat that template is a parameter of item
<tr data-ng-repeat="item in items" >
<td data-ng-repeat="column in columns" ng-bind-html="resolve(item,column)"></td>
</tr>
controller code :
$scope.columns:[{ name: "id" },
{ name: "name" },
{ name: "lastName"},
{ name: "name", template: "<a href='#/xx/{{item.id}}' >edit</a>"},
{ name: "name", template: "<button ng-click='del()' href='#'>del</button>" }]
and resolve function is:
$scope.resolve = function(item,col){
if (col.template) {
// i dont know for this
} else {
return sce.trustAsHtml("<span>" + item[col.name] + "</span>");
}
}
Any help is much appreciated,
Thanks
I have already done a sample like as your sample here. In this sample, I created more a custom directive named "dynamic" to resolve and compile the HTML string automatically in AngularJS.
HTML
<table ng-app="myApp" ng-controller="templateController">
<tbody>
<tr ng-repeat="item in items">
<td ng-bind="item.name"><td>
<td dynamic="item.template"></td>
</tr>
<tbody>
</table>
Javascript
var myApp = angular.module('myApp', []);
myApp.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function(html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
myApp.controller('templateController', function ($scope){
$scope.items = [{ name: "id" },
{ name: "name" },
{ name: "lastName"},
{ id: 1, name: "name", template: "<a href='#/xx/{{item.id}}' >edit</a>"},
{ name: "name", template: "<button ng-click='del()' href='#'>del</button>" }];
});
Hope this will help you!!
I am making a few speculations on your logic, but this smells to me like the job of a directive.
Check out the plunker i made to show my point:
http://plnkr.co/edit/zW6LYKNQxgfRszwY0eV2?p=preview
Also looking at the example code you have given:
$scope.columns:[{ name: "id" },
{ name: "name" },
{ name: "lastName"},
{ name: "name", template: "<a href='#/xx/{{item.id}}' >edit</a>"},
{ name: "name", template: "<button ng-click='del()' href='#'>del</button>" }]
$scope.columns = not :

AngularJS : cannot connect factory to controller

I'm sorry if this is an easy question. I'm new and probably don't understand the right things to search for to find the answer.
I've basically followed this angularJS tutorial http://www.youtube.com/watch?v=i9MHigUZKEM
I've gotten through all of it except setting up a factory that connects to my controller.
Here is the code for the factory:
demoApp.factory('simpleFactory', function(){
var people = [
{ name: 'Will', age: '30' },
{ name:'Jack', age:'26' },
{ name: 'Nadine', age: '21' },
{ name:'Zach', age:'28' }
];
var factory = {};
factory.getPeople = function() {
return people;
};
});
Here is the controller:
demoApp.controller('FirstCtrl', ['$scope', 'simpleFactory', function ($scope, simpleFactory) {
$scope.people = simpleFactory.getPeople();
}]);
And just a simple repeat in the HTML:
Name:
<input type="text" ng-model="filter.name"> {{ nameText }}
<ul>
<li ng-repeat="person in people | filter:filter.name | orderBy: 'name'">{{ person.name }}- {{ person.age }}</li>
</ul>
The error I get is "TypeError: Cannot call method 'getPeople' of undefined" in the javascript console.
Note: This all works correctly when within the controller I have the data object hardcoded in like so:
demoApp.controller('FirstCtrl', ['$scope', 'simpleFactory', function ($scope, simpleFactory) {
$scope.people = [
{ name: 'Will', age: '30' },
{ name:'Jack', age:'26' },
{ name: 'Nadine', age: '21' },
{ name:'Zach', age:'28' }
];
}]);
A small change in your service;
demoApp.factory('simpleFactory', function(){
var people = [
{ name: 'Will', age: '30' },
{ name:'Jack', age:'26' },
{ name: 'Nadine', age: '21' },
{ name:'Zach', age:'28' }
];
return {
getPeople: function() {
return people;
};
}
});
And in your controller
demoApp.controller('FirstCtrl', ['$scope', 'simpleFactory', function ($scope, simpleFactory) {
$scope.people = simpleFactory.getPeople();
}]);
Essentially you're just missing the return statement from your 'factory' object, but it might be clearer if you do a little renaming as well to avoid confusion.
Example, adapted from the AngularJS book:
demoApp.factory('People', function(){ // Renamed factory to be more descriptive
var people = {}; // Renamed 'factory' to 'people', as an object we can prototype more functions later
people.query = function() { // called query, but could be getPeople
return [ // just return a static array for now
{ name: 'Will', age: '30' },
{ name:'Jack', age:'26' },
{ name: 'Nadine', age: '21' },
{ name:'Zach', age:'28' }
];
}
return people;
});
So now your controller can pull this information in:
demoApp.controller('FirstCtrl', ['$scope', 'People', function ($scope, People) { // dependency injection
$scope.people = People.query();
});
So now we have a descriptive factory for People that returns an object, one of which is called query. We can later update this query function to read in parameters, fire off AJAX calls and do some complicated stuff. But one step at a time :)

Categories

Resources