AngularJS nested ng-repeat - javascript

I have a html like this :
<div class="fields-plan"data-ng-repeat="roomname in assign.roomname">
<section>
<span>Room: {{roomname}}</span>
</section>
<ul data-ng-repeat="room in assign.rooms.roomname">
<li>
{{room.room}}
</li>
<ul>
</div>
and my angular controller look like this:
var room = {"1.2":
[
{room: "1.2.1"},
{room: "1.2.2"},
{room: "1.2.3"}
],
"1.3": [
{room: "1.3.1"},
{room: "1.3.2"},
{room: "1.3.3"}
]};
var keys = Object.keys(room);
this.roomname = keys;
this.rooms = room;
In my second ng repeat, it doesn't work and how can i loop based on roomname, that output from the first ng repeat??

Your second ng-repeat needs to take the first ng-repeat value instead of directly taking the room name, so your second ng-repeat should look like this:
Code:
<div class="fields-plan"data-ng-repeat="(key, value) in assign.rooms">
<section>
<span>Room: {{key}}</span>
</section>
<ul data-ng-repeat="room in value">
<li>
{{room.room}}
</li>
<ul>
</div>

You can achieve this by slightly reformatting your Json and then using the following code:
The key is to use the value of the first ng-repeat in the second ng-repeat and not trying to reference the first collection.
html:
<div ng-controller="MyCtrl">
<div class="fields-plan" ng-repeat="room in rooms">
<section>
<span>Room: {{room.name}}</span>
</section>
<ul ng-repeat="subroom in room">
<li>
{{room.subRoom}}
</li>
<ul>
</div>
</div>
javascript:
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.rooms = [ { name : "1.2",
subRoom: [
"1.2.1","1.2.2","1.2.3"],
}, { name: "1.3",
subRoom: [
"1.3.1","1.3.2","1.3.3"]}];
}
Fiddle demo: http://jsfiddle.net/0pnam0wj/

Is this you wanted?
plnkr
<div data-ng-repeat="(roomnamePrefix, roomname) in rooms">
<section>
<span>Room: {{roomnamePrefix}}</span>
</section>
<ul data-ng-repeat="room in roomname">
<li>
{{room.room}}
</li>
<ul>
</div>

Related

match parent and inner index within nested ng-repeats while passing duplicate values

I am trying to pass duplicate values in different formats but can not match parent and inner indexes hence I get Error: [ngRepeat:dupes]. that said, I pass object with multiple properties among which I have tags...see below
vm.hotels returns objects like below
0:object
tags:"tag1|tag2|tag3"
1:object
tags:"tag1|tag2|tag3"
vm.hTags is an array that matches each object like below
["tag1", "tag2", "tag3"]
within my controller I split tags and push then into an array within a loop which I pass to the view. this works as it should but I can not make it work with indexes within the view. below are nested ng-repeats
<li ng-repeat="item in vm.hotels track by $index">
<ul>
<li ng-repeat="tag in vm.hTags[$index]">
{{tag}}
</li>
</ul>
<li>
I tried to use vm.hTags[$parent.$index] but it does not work as duplicate error is thrown due to indexes. Perhaps I need to create some custom tracking property ?
The problem with nested ng-repeat is that is that if you use $index for both child and parent, it might conflict. In order to avoid it, we can use it by giving name. Like this
ng-repeat="(hotelIndex, item) in vm.hotels ..."
I don't know how you want to render it but here's a sample example of that:
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope) {
var vm = this;
vm.hotels = [{
tags: "tag1|tag2|tag3"
}, {
tags: "tag4|tag5|tag6"
}]
vm.hTags = [["tag1", "tag2", "tag3"], ["tag4", "tag5", "tag6"]]
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp" ng-controller="MainCtrl as vm">
<div ng-repeat="(hotelIndex, item) in vm.hotels track by hotelIndex">
<div ng-repeat="tag in vm.hTags[hotelIndex]">
{{tag}}
</div>
<br>
</div>
</body>
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope) {
var vm = this;
vm.hotels = [{
tags: "tag1|tag2|tag3"
}, {
tags: "tag4|tag5|tag6"
}]
vm.hTags = [["tag1", "tag2", "tag3"]];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.min.js"></script>
<body ng-app="myApp" ng-controller="MainCtrl as vm">
<div ng-repeat="key in vm.hotels">
<div ng-repeat="tag in vm.hTags[$index]">
{{tag}}
</div>
</div>
<br>
<div ng-repeat="(key, value) in vm.hotels">
<div ng-repeat="tag in vm.hTags[key]">
{{tag}}
</div>
</div>
</body>
Please find the code change below,
<li ng-repeat="item in vm.hotels track by $index">
<ul>
<li ng-repeat="tag in vm.hTags[$index]">
{{tag}}
</li>
</ul>
<li>
Check and let me know.You made a syntax error

Angular: Async push to recursively generated array

A quick explanation: this is a simple app meant to recreate Reddit, so each comment on a post (which I'm calling a "node") contains an array of comments (nodeList), and each comment can have any number of nodes in this list. What I want to do is push an added comment into the current ng-repeat object's "nodeList", so that I can add the comment without refreshing.
This recursive template works, but only when I refresh the page, so it isn't being pushed into the current scope of the ng-repeat. I've done some research about $index, but because it's recursively generated there is no way of knowing how deep down the tree of nested arrays you could be.
What I tried was $scope.x.nodeList = [] and then pushing a new "node" into that, thinking that each instance of "x" is a new $scope within the ng-repeat, but this is not working.
Controller:
redditLite.controller('post', function($routeParams, $scope, $http) {
var postId = $routeParams.id;
var controller = this;
var postComment = {};
var node = {};
$scope.show = true;
$scope.addComment = {};
$scope.post = {};
$scope.x = {};
$scope.x.nodeList = [];
controller.comment = function(parentId) {
postComment.comment = $scope.addComment.body;
postComment.parentId = parentId;
postComment.rootPostId = $scope.post.id;
$http.post('add-comment',postComment, config).then(function(response) {
node = response.data;
$scope.x.nodeList.push(node);
});
}
});
HTML:
<script type="text/ng-template" id="postTree">
<div class="username">
{{x.username}}
</div>
<div class="body">
{{x.body}}
</div>
<div class="metrics">
<ul>
<li>
Likes: {{x.likes}}
<br>
<button class="like-button" ng-click="controller.likeNode(x.nodeId); x.likes = x.likes + 1">Like</button>
</li>
<li>
Comments: {{x.cmmnts}}
<br>
<button class="comment-button" ng-click="show = !show" ng-show="show">Comment!</button>
<div class="comment-field" ng-hide="show">
<input type="text" placeholder="Enter a comment." ng-model="addComment.body">
<br>
<button ng-click="controller.comment(x.nodeId); show = !show">Submit</button>
<br>
<button ng-click="show = !show">Cancel</button>
</div>
</li>
<li>
Date: {{x.submit_date}}
</li>
</ul>
</div>
<div class="nodes">
<ul ng-if="x.nodeList" ng-model="x.nodeList">
<li ng-repeat="x in x.nodeList" ng-include="'postTree'"></li>
</ul>
</div>
</script>
<div class="post">
<div class="username">
{{post.username}}
</div>
<div class="body">
{{post.body}}
</div>
<div class="metrics">
<ul>
<li>
Likes: {{post.likes}}
<br>
<button class="like-button" ng-click="controller.like(post.id); post.likes = post.likes + 1">Like</button>
</li>
<li>
Comments: {{post.cmmnts}}
<br>
<button class="comment-button" ng-click="show = !show" ng-show="show">Comment!</button>
<div class="comment-field" ng-hide="show">
<input type="text" placeholder="Enter a comment." ng-model="addComment.body">
<br>
<button ng-click="controller.comment(post.id); show = !show">Submit</button>
<br>
<button ng-click="show = !show">Cancel</button>
</div>
</li>
<li>
Date: {{post.submit_date}}
</li>
</ul>
</div>
</div>
<ul class="master-list">
<li ng-repeat="x in post.nodeList" ng-include="'postTree'"></li>
</ul>
There is some missing logic that needs to be handled, but for now I'm trying to get this working to push an object into an array no matter how deeply nested that array is within other objects.
Edit: I've included an image of the JSON structure as a referencece, and it can be seen that each node object can contain an array of node objects, so this is the array that I am attempting to push a new node into.
I've done recursive templating like this successfully several times. If I recall, having the ng-repeat and ng-include on the same html tag is problematic. Try wrapping the contents of your script in a div, remove the ng-include attribute from your li elements, and instead add an ng-include element inside of your li elements. Also, you have basically added your template twice, once for the root item, then again to add recursive behavior. You should be able to only have 1 template (the script one) if you wrap your post variable inside of an array in your markup.
<script type="text/ng-template" id="postTree">
<div>
...
<div class="nodes">
<ul ng-if="x.nodeList" ng-model="x.nodeList">
<li ng-repeat="x in x.nodeList">
<ng-include src="'postTree'"></ng-include>
</li>
</ul>
</div>
</div>
</script>
<ul class="master-list">
<li ng-repeat="x in [ post ]">
<ng-include src="'postTree'"></ng-include>
</li>
</ul>
Update:
When comment is clicked, send the parent to the function instead of just the parent id. This way, you can push the child onto it's parent after the $http post completes.
JS:
angular
.module('redditLite', [])
.controller('post', function($scope, $http) {
var controller = this;
$scope.show = true;
$scope.post = {
id: 1,
parentId: null,
username: 'jeff',
body: 'my fantastic comment',
likes: 152,
submit_date: new Date(),
nodeList: []
};
$scope.comment = function(parent) {
var postComment = {
id: 2,
parentId: parent.id,
username: 'jeff',
body: parent.addComment,
likes: 0,
submit_date: new Date(),
nodeList: []
};
console.log('adding comment', postComment, parent)
$http.post('add-comment',postComment, config).then(function(response) {
var node = response.data;
parent.nodeList.push(node);
});
}
});
HTML
<script type="text/ng-template" id="postTree">
<div>
<div class="username">
{{x.username}}
</div>
<div class="body">
{{x.body}}
</div>
<div class="metrics">
<ul>
<li>
Likes: {{x.likes}}
<br>
<button class="like-button" ng-click="controller.likeNode(x.nodeId); x.likes = x.likes + 1">Like</button>
</li>
<li>
Comments: {{x.nodeList.length}}
<br>
<button class="comment-button" ng-click="show = !show" ng-show="show">Comment!</button>
<div class="comment-field" ng-hide="show">
<input type="text" placeholder="Enter a comment." ng-model="x.addComment">
<br>
<button ng-click="comment(x); show = !show">Submit</button>
<br>
<button ng-click="show = !show">Cancel</button>
</div>
</li>
<li>
Date: {{x.submit_date}}
</li>
</ul>
</div>
<div class="nodes">
<ul ng-if="x.nodeList" ng-model="x.nodeList">
<li ng-repeat="x in x.nodeList">
<ng-include src="'postTree'"></ng-include>
</li>
</ul>
</div>
</div>
</script>
<ul class="master-list">
<li ng-init="x = post">
<div ng-include="'postTree'"></div>
</li>
</ul>
Plunker:
https://plnkr.co/edit/X40JoHduKYy12QPuLBCo?p=preview
You might be getting hurt by lexical scope. Basically:
$http.post('add-comment',postComment, config).then(function(response) {
node = response.data;
$scope.x.nodeList.push(node); //this $scope is not actually your $scope
});
Try doing:
var vm = this;
$http.post('add-comment',postComment, config).then(function(response) {
node = response.data;
vm.$scope.x.nodeList.push(node); //should be the right scope.
})
If this doesn't work, try double checking your bindings. A value not updating normally means the binding is not correct.
Try using $scope.x.nodeList.concat([node]); instead of pushing the value, like this avoid mutations in the object.

ng-tags-input with dynamic model

how can i use ng-tags-input inside a ng-repeat loop, when each element has different tags? How can i set the ng-model dynamically?
<div ng-controller="myController">
<ul>
<li ng-repeat="file in files">
{{file}} <tags-input ng-model="tags"></tags-input>
</li>
</ul>
</div>
app.controller('myController', function ($scope) {
$scope.tags = ['tagA','tagB'];
// $scope.tags['file1'] = ['tagA','tagB'];
// $scope.tags['file2'] = ['tagC','tagD'];
});
Thanks in advance!
I have the same solution as Claies suggested but I tried my self.
Please check working example : http://plnkr.co/edit/bNQ6DrUlNWdEAi8fnvBr?p=preview
HTML
<ul>
<li ng-repeat="file in files">
{{file.name}}
<tags-input ng-model="file.tags"></tags-input>
</li>
</ul>
Controller
var app = angular.module('plunker', ['ngTagsInput']);
app.controller('MainCtrl', function($scope, $http) {
$scope.files = [
{
name : 'file1','tags': [{text: 'tagA'},{text: 'tagB'}]
},
{
name : 'file2','tags': [{text: 'tagC'},{text: 'tagD'}]
}
];
});
You can use the ng-model to form a more complex expression such as tags[file], assuming file is the string corresponding to the key in your dictionary.
<div ng-controller="myController">
<ul>
<li ng-repeat="file in files">
{{file}} <tags-input ng-model="tags[file]"></tags-input>
</li>
</ul>
</div>
$scope.tags = {
'first': [{text: 'Tag1'}, {text: 'Tag2'}],
'second': [{text: 'Tag3'}, {text: 'Tag4'}]
};
$scope.files = ['first', 'second'];
See plunker example

Can I set a variable inside ng-repeat?

I have an application that has two forms that are lists of buttons and a form with a list of labels. I choose a person, choose a phone, and on the third form I show a list of phones and their associated people (location). Is it possible to set a variable with ng-click inside an ng-repeat block? I tried settings a variable _person to be equal to the the button text, which would be {{person}}, but _person doesn't seem to be set to anything when I print it on the next form. I'm also not sure if I used ng-init correctly in the first <div>, and should I be using ng-model at all?
<div ng-init="showSelectUser=true; _person=''">
<div class="selectUser" ng-show="showSelectUser">
<h2>Who are you?</h1>
<ul ng-click="showSelectUser=false; showDeviceForm=true;">
<li ng-repeat="person in people">
{{person}}
</li>
</ul>
</div>
<div class="selectDevice" ng-show="showDeviceForm" ng-click="showDeviceForm=false; showDeviceList=true">
<p>person: {{_person}}</p>
<h2>Which phone?</h2>
<ul>
<li ng-repeat="device in devices">
<a class="btn btn-default" href="#" role="button" ng-click="device.location=_person">{{device.name}}</a>
</li>
</ul>
</div>
<div class="devicesView" ng-show="showDeviceList">
<ul>
<li ng-repeat="device in devices">
<h3>{{device.name}}</h3>
<h4 class="deviceLocation">{{device.location}}</h4>
</li>
</ul>
</div>
</div>
angular.module('devicesApp')
.controller('MainCtrl', function ($scope) {
$scope.devices = [
{name: 'iPhone 4', location: 'Desk'},
{name: 'iPhone 5', location: 'Desk'},
{name: 'iPhone 6', location: 'Desk'},
];
$scope.people = [
'John',
'Scott',
'Adam'
];
});
The ngRepeat directive creates a new scope, so if you have ng-click="device.location=_person" the device model will be created in that scope if it not exists. So make sure that device already exists in a parent scope, for example by setting $scope.device = {} in your controller.
_person is undefined, because it is defined in the inner scope of the ng-repeat="person in people". Currently your ng-click sets every device location to undefined.
In general you will have a lot of scope inheritance issues when using expressions in ngClicks instead of doing something like ng-click="setPerson()" and have $scope.setPerson = function () { ... } in your controller.
Please clarify what you want to do in more high level terms and I will update my answer.
Edit
Something like this looks more logical for me. However, I think you will have a lot of UX issues because the user is not able to change its choice after clicking one of the list items.
Note that if you put business logic in JS files instead of templates, it is easier to test.
<div ng-app="devicesApp" ng-controller="MainCtrl">
<div class="selectUser" ng-show="step === 'step1'">
<h2>Who are you?</h2>
<ul>
<li ng-repeat="person in people">
{{person}}
</li>
</ul>
</div>
<div class="selectDevice" ng-show="step === 'step2'">
<p>person: {{selected.person}}</p>
<h2>Which phone?</h2>
<ul>
<li ng-repeat="device in devices"> <a class="btn btn-default" href="#" role="button" ng-click="selectDevice(device)">{{device.name}}</a>
</li>
</ul>
</div>
<div class="devicesView" ng-show="step === 'step3'">
<ul>
<li ng-repeat="device in devices">
<h3>{{device.name}}</h3>
<h4 class="deviceLocation">{{device.location}}</h4>
</li>
</ul>
</div>
</div>
In JS file:
angular.module('devicesApp', []).controller('MainCtrl', function ($scope) {
$scope.devices = [{
name: 'iPhone 4',
location: 'Desk'
}, {
name: 'iPhone 5',
location: 'Desk'
}, {
name: 'iPhone 6',
location: 'Desk'
}, ];
$scope.people = [
'John',
'Scott',
'Adam'];
$scope.selected = {};
$scope.step = 'step1';
$scope.selectPerson = function (person) {
$scope.selected.person = person;
$scope.step = 'step2';
};
$scope.selectDevice = function (device) {
device.location = $scope.selected.person;
$scope.step = 'step3';
}
});
See this JSFiddle.

Filter PHP list with angularJS

I am trying to create a blog page and I chose WordPress over AngularJS so Google can index the page ( or at least that's what i think it works). So for now I have a list which looks like this
<ul>
<li id="1">
<h2>My first Post</h2>
<p>The Message...</p>
</li>
<li id="2">
<h2>My second Post</h2>
<p>The Message...</p>
</li>
<li id="3">
<h2>My third Post</h2>
<p>The Message...</p>
</li>
</ul>
but PHP is pretty static so I want to create a angular filter to filter posts by title, but I don't really know how to do this.
I was thinking to create a hide class for <li> items and somehow if a post should be deleted because of the filter, to add the hide class to it. I try to mix this angular so I can have a dynamic search instad loading the page again after searching.
Considering you don't have a service that will return only the JSON formatted items, the best approach would be creating a directive that remove the li, parse their contents to an object and use ng-repeat in a template. Something like this:
var app = angular.module('plunker', []);
app.directive('filtered', function() {
return {
scope: {
criteria: '=filtered'
},
compile: function(elm, attr) {
var entries = [];
elm.find('li').each(function(index, item) {
var entry;
$item = angular.element(item);
entries.push({
id: $item.attr('id'),
title: $item.find('h2').text(),
body: $item.find('p').text()
});
}).remove();
elm.append(
'<li ng-repeat="entry in entries | filter:{title: criteria}" id={{entry.id}}>' +
'<h2>{{entry.title}}</h2>' +
'<p>{{entry.body}}</p>' +
'</li>'
);
return function(scope) {
scope.entries = entries;
};
}
};
});
And in your HTML you just decorate the list with the directive:
<input ng-model="userCriteria">
<ul filtered="userCriteria">
<li id="1">
<h2>My first Post</h2>
<p>The Message...</p>
</li>
<li id="2">
<h2>My second Post</h2>
<p>The Message 2...</p>
</li>
<li id="3">
<h2>My third Post</h2>
<p>The Message 3...</p>
</li>
</ul>
I've put together a Plnkr here. Go ahead and change the HTML list and it will automatically include that items.
You could create a directive to wrap the html content you receive from php, pass the filter term and which element of the list you want to check).
Here is a plunker: http://plnkr.co/edit/Bv2opi5CHfJa0pQyFrBc?p=preview
(this require jquery to hide and show, but you can use css({'display':'none|block'}) too)
(maybe you could modify the directive to apply the filter term to ignore the case of the words)
app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.model = {
filter: ''
};
});
app.directive('myHtmlFilter', [function() {
return {
restrict: 'A',
scope: {
filter: '=myHtmlFilter',
element: '#'
},
link: function(scope, elem, attrs) {
scope.$watch('filter', function(newval, oldval) {
elem
.find('ul>li')
.hide()
.find(scope.element)
.filter(':contains("'+scope.filter+'")')
.parent()
.show();
})
}
}
}]);
index.html
<input type="text" ng-model="model.filter" />
<div my-html-filter="model.filter" element="h2">
<ul>
<li id="1">
<h2>My first Post</h2>
<p>The Message...</p>
</li>
<li id="2">
<h2>My second Post</h2>
<p>The Message...</p>
</li>
<li id="3">
<h2>My third Post</h2>
<p>The Message...</p>
</li>
</ul>
</div>
Edit I updated the plunker with a more complete example than the code shown here.
If you can have the JSON approach, then Angular automatically does that for you.
Just go with a simple filter solution:
<input ng-model="criteria"/>
<ul>
<li ng-repeat="entry in entries | filter:{title: criteria}" id="{{entry.id}}">
<h2>{{entry.title}}</h2>
<p>{{entry.body}}</p>
</li>
</ul>
In your controller (or any JS with access to the container scope):
app.controller('MainCtrl', function($scope) {
$scope.criteria = "Title";
$scope.entries = [
{
id: 1,
title: 'My title',
body: 'contents...'
},
{
id: 2,
title: 'The other content',
body: 'contents...'
},
{
id: 3,
title: 'Another title',
body: 'contents...'
},
{
id: 4,
title: 'Something completely different',
body: 'contents...'
}
];
});
You can even use $http service to retrieve the JSON file:
app.controller('MainCtrl', function($scope) {
$scope.criteria = "Title";
$scope.entries = $http.get('path/to/entries.json');
});

Categories

Resources