in angular how to add nested object to ng-repeat - javascript

I'm a complete angular noob so please forgive the rudimentary question.
I have a post/comment structure for my model that looks like this:
[
{
"title": "awesome post",
"desc": "asdf",
"comment_set": [
{
"id": 2,
"desc": "another awesome comment",
}
]
}
]
My angular template looks like the following:
<li ng-repeat="post in $ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.created_at" class="media media-clearfix-xs">
<div>{{post.title}}</div>
<ul class="comments">
<li ng-repeat="comment in post.comment_set | orderBy:created_at" clas="media">
<div>{{comment.desc}}
</li>
<li class="comment-form">
<div class="input-group">
<form ng-submit="$ctrl.addComment()">
<input ng-model="post.comment_set.desc" type="text" class="form-control" />
</form>
<span class="input-group-btn">
<input type="submit" class="btn btn-default"><i class="fa fa-photo"></i></input>
</span>
</div>
</li>
</ul>
</li>
My angular component looks like this:
angular.
module('phoneList').
component('phoneList', {
templateUrl: '/static/common/angular/phone-list/phone-list.template.html',
controller: ['$http',
function PhoneListController($http) {
var self = this;
self.addComment = function() {
self.push({desc:post.comment_set.desc});
};
$http.get('api/posts/').then(function(response) {
self.phones = response.data;
});
}
]
});
What am I missing that will add comments to the comments ng-repeat?

Your addComment function is incorrect.
You should have:
ng-submit="addComment(post.comment_set)"
and:
ng-model="$ctrl.desc"
Then, addComment() should be:
self.addComment(comment_set){
comment_set.push({desc: self.desc, id: <ID_HERE>});
self.desc = null;
}
This way, you reset the desc variable so you can add a new comment

Related

Angular filter on object property inside an array

I am having an array of objects inside the $scope. The object has the name and 'attributes' property of which the attributes is an object. I have a text field which I need to bind to a model which should be used to filter the state based on either the name or noOfCitizens. However, the below code is not filtering the items. Where I am going wrong.
I am working with Angularjs 1.5.8 version
//Inside the controller
$scope.states=[];
var mp = {};
mp.name = "MP";
mp.attributes= {
"name":"MP",
"noOfCitizens":"~ 900000"
};
var ts = {};
ts.name = "TS";
ts.attributes= {
"name":"TS",
"noOfCitizens":"~ 8000"
};
$scope.states.push(mp);
$scope.states.push(ts);
<!-- Inside my html page -->
<div style="margin-left: 10px">
<input type="text" ng-model="state.attributes['name']" placeholder="filter">
</div>
<div class="col-lg-3" ng-repeat="state in states | filter:state.attributes['name']">
<h2>{{state.name}}</h2>
<ul>
<li>Name: {{state.attributes['name']}}</li>
<li>No Of Citizens: {{state.attributes['noOfCitizens']}}</li>
</ul>
</div>
Change your ng-model directive and the option passed to the filter pipe as follows,
ng-model="ctrl.stateFilter"
ng-repeat="state in ctrl.states | filter : ctrl.stateFilter"
Check the below code snippet.
angular
.module('demo', [])
.controller('DefaultController', DefaultController);
function DefaultController() {
var vm = this;
vm.states = [];
var mp = {
name: "MP",
attributes: {
"name": "MP",
"noOfCitizens": "~ 900000"
}
};
var ts = {
name: "TS",
attributes: {
"name": "TS",
"noOfCitizens": "~ 8000"
}
};
var vs = {
name: "VS",
attributes: {
"name": "VS",
"noOfCitizens": "~ 8000"
}
};
vm.states.push(mp);
vm.states.push(ts);
vm.states.push(vs);
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demo">
<div ng-controller="DefaultController as ctrl">
<div style="margin-left: 10px">
<input type="text" ng-model="ctrl.stateFilter" placeholder="filter">
</div>
<div class="col-lg-3" ng-repeat="state in ctrl.states | filter : ctrl.stateFilter">
<h2>{{state.name}}</h2>
<ul>
<li>Name: {{state.attributes.name}}</li>
<li>No Of Citizens: {{state.attributes.noOfCitizens}}</li>
</ul>
</div>
</div>
</div>

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.

Angular JS expressions not evaluating

I've the following simple Angular JS trail, which contains the basic CRUD operations:
<html>
<head>
<title>CRUD</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
</head>
<body>
<div ng-app>
Simple Expression Evaluator:<br/>
<input ng-model="calculator"/><br/>
{{calculator + "=" + $eval(calculator)}}
</div>
<h3>CRUD - Comments</h3>
<div ng-app="commentapp">
<ul ng-controller="commentController">
<li ng-repeat="user in users">
{{user.name}} wrote "{{user.comment}}"
<br/>Delete
Edit
</li>
<li>
<input id="name" ng-model="current.name" value="{{current.name}}" />
<input id="name" ng-model="current.comment" value="{{current.comment}}" />
</li>
<li>
<button ng-click="save(current)">
Save
</button>
<button ng-click="addNew(current)">
Add New User
</button>
</li>
</ul>
</div>
<script>
var app = angular.module("commentapp", []);
app.controller("commentController", function($scope) {
$scope.users = [{
"name": "Qwe",
"comment": "Great!"
}];
$scope.current = {};
$scope.addNew = function(user) {
$scope.users.push(user);
};
$scope.edit = function(user) {
$scope.current = user;
};
$scope.save = function(user) {
$scope.current = {};
};
$scope.remove = function(user) {
var index = $scope.users.indexOf(user);
$scope.users.splice(index, 1);
};
});
</script>
</body>
</html>
However the output shows:
So, the expression evaluator works perfectly, which means Angular JS is tied up correctly. But the rest of the components don't work at all. Instead of Qwe, I get the expression {{user.name}}. What am I doing wrong here?
Hi I tried your script and seem working when I remove the first ng-app
nov the html part is like this
<h3>CRUD - Comments</h3>
<div ng-app="commentapp">
<ul ng-controller="commentController">
<li ng-repeat="user in users">
{{user.name}} wrote "{{user.comment}}"
<br/>Delete
Edit
</li>
<li>
<input id="name" ng-model="current.name" value="{{current.name}}" />
<input id="name" ng-model="current.comment" value="{{current.comment}}" />
</li>
<li>
<button ng-click="save(current)">
Save
</button>
<button ng-click="addNew(current)">
Add New User
</button>
</li>
</ul>
</div>
look at this fiddle
hope this help

ng-show Displaying all items

Continue working with angularjs and now having problem with ng-show which on click it show me all hidden data. As I understand, I need to specify ID of clicked item which I want to show, from my example i'm using ng-model which had boolean value and on click it change on true, that's why it's showing all items. Tell me please, how can I show item which I had selected?
<div class="list-group" ng-click="SetItemVisible()" ng-repeat="q in feed">
<a href="" class="list-group-item">
<h4 ng-model="showItem" class="list-group-item-heading">{{q.title}}</h4>
<p ng-show="showItem" value="true" class="list-group-item-text">{{q.body}}</p>
</a>
</div>
And js:
$scope.SetItemVisible = function () {
if (!$scope.showItem) {
$scope.showItem = true;
} else {
$scope.showItem = false;
}
}
$scope.feed = [];
function getRssItems() {
rssFeedService.getFeed().then(function (results) {
$scope.feed = results.data;
}, function (error) {
//alert(error.data.message);
});
}
You can do dis by:
$scope.feed = [{
'title': "A",
'body': "testA body"
},
{
'title': "b",
'body': "testb body"
}
]
$scope.showItem = {};
$scope.SetItemVisible = function (index) {
$scope.showItem[ index] = true;
}
<div class="list-group" ng-click="SetItemVisible($index)" ng-repeat="q in feed track by $index">
<a href="" class="list-group-item">
<h4 ng-model="showItem[$index]" class="list-group-item-heading">{{q.title}}</h4>
<p ng-show="showItem[$index]" value="true" class="list-group-item-text">{{q.body}}</p>
</a>
For live demo click here: http://plnkr.co/edit/ApI9eb8eQlBdoMUkn8do?p=preview
<div class="list-group" ng-click="q.showItem != q.showItem" ng-repeat="q in feed">
<a href="" class="list-group-item">
<h4 ng-model="showItem" class="list-group-item-heading">{{q.title}}</h4>
<p ng-show="q.showItem" value="true" class="list-group-item-text">{{q.body}}</p>
</a>
</div>
Assuming following JSON for feed
[
{
"title":"test1",
"body":"test body 1",
"show":false
},
{
"title":"test2",
"body":"test body 2",
"show":false
}
]
HTML
<body ng-controller="MainCtrl">
<div class="list-group" ng-repeat="q in feed">
<a class="list-group-item">
<h4 ng-click="q.show=!q.show" class="list-group-item-heading">{{q.title}}</h4>
<p ng-show="q.show" value="true" class="list-group-item-text">{{q.body}}</p>
</a>
</div>
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $http) {
$scope.feed = [];
$http.get('feed.json').then(function (results) {
$scope.feed = results.data;
}, function (error) {
//alert(error.data.message);
});
});
Check out here

Pick out a parameter from ng-repeat and repeat the selected properties

I want to display the json in a certain format with ng-repeat, but have had no success.
This is the structure:
<div ng-repeat="book in books">
{{book}}
<div ng-repeat="name in book.name" >{{name}} </div>
</div>
THis is the structure how I want it to display on the page:
Genre:
Sci-fic
Bookname1
Bookname2
Non sci-fic
Bookname3
Bookname4
This is the json sample:
[{"Genre":Sci-fic,"Name":"Bookname1"},
{"Genre":Sci-fic,"Name":"Bookname2"},
{"Genre":Non sci-fic,"Name":"Bookname3"},
{"Genre":Non sci-fic,"Name":"Bookname4"}]
http://jsfiddle.net/HB7LU/4053/
Should/Need to fix/restructure your JSON
$scope.books = [
{
"genre": "Sci-fic",
"titles" : ['Bookname1', 'Bookname2']
},
{
"genre": "Non sci-fic",
"titles" : ['Bookname3', 'Bookname4']
},
];
<div ng-repeat="book in books">
{{book.genre}}
<div ng-repeat="title in book.titles" >{{title}}</div>
</div>
Wouldn't it be easier if you format the json object that you receive like this?:
[{
"name": "Sci-Fic",
"names":["book 1","book 2"]
},
{
"name": "Another Genre",
"names":["book 1","book 2"]
}
]
Why don't u use ul and li? just another approach.
<ul>
<li ng-repeat="genre in books">
{{genre.name}}
<ul>
<li ng-repeat="name in genre.names">{{name}}</li>
</ul>
</li>
</ul>
Hope it helps :D
---Edit---
Sry, didn't see #Christopher Marshall answer while i was writing, its the same thing.
If the data MUST be in that format you can do this:
<div ng-app="app">
<div ng-controller="ParentCtrl">
<div data-ng-repeat="genre in data | orderBy:'Genre'">
<span data-ng-if="(data | orderBy:'Genre')[$index - 1].Genre != genre.Genre">
{{ genre.Genre }}
</span>
<div style="padding-left: 30px;">{{ genre.Name }}</div>
</div>
</div>
</div>
Here's a jsFiddle of it: http://jsfiddle.net/wgLnH/

Categories

Resources