Angular: Async push to recursively generated array - javascript

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.

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 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

AngularJS: How to prevent recursive templating from looping forever

I've written a set of code which, when bound to an object, will display the key and value of every property in that object.
<script type="text/ng-template" id="object-displayer">
<div ng-if="recurrable(value)">
<span class="recurrable">
<span class="key">{{key}}</span>
(<span class="type" ng-bind="type(value)"></span>)
</span>
<ul class="indent">
<li ng-repeat="(key, value) in value track by $index" ng-include="'object-displayer'"></li>
</ul>
</div>
<div ng-if="!recurrable(value)">
<span class="primitive">
<span class="key">{{key}}</span> (<span class="type" ng-bind="type(value)"></span>): <span class="value">{{value}}</span>
</span>
</div>
</script>
And within the controller:
$scope.recurrable = function (value) {
return typeof value === 'object';
}
$scope.type = function (value) {
return typeof value;
}
jsbin: (https://jsbin.com/xusiteluwi/1/edit?html,js,output)
This works great when your nested objects create a simple tree structure. But it throws an exception when you create a loop: if one of your properties references any object upstream, it will display until the digest limit is reached (usually 10).
Now here's my question. Is there some way in angular controller/templating to ng-if whether an object has already been shown, thus catching any loops and displaying only a single iteration?
Bonus points if you can come up with a solution that will apply globally in the graph, or a solution that will only track the current branch.
Start with an empty visitedReferences.
Each time you encounter any reference (object) check if it is in the visitedReferences and if so don't follow this reference, otherwise add it to the visitedReferences.
Hope Help You!
angular
.module('test', [])
.controller('RecursiveCtrl', function($scope) {
var vm = $scope;
vm.data = {
a: 'Hello',
b: {
c: 'World'
}
};
vm.isRecursive = function(value) {
return angular.isObject.call(this, value);
};
})
;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<article data-ng-app="test">
<div data-ng-controller="RecursiveCtrl">
<script type="text/ng-template" id="tpl-recursive">
<div ng-switch="isRecursive(value)">
<div ng-switch-when="false" >
<strong ng-bind="prop"></strong>: <span ng-bind="value"></span>
</div>
<div ng-switch-when="true">
<ol>
<li ng-repeat="(prop, value) in value" ng-include="'tpl-recursive'">
</li>
</ol>
</div>
</div>
</script>
<ol>
<li ng-repeat="(prop, value) in data" ng-include="'tpl-recursive'"></li>
</ol>
</div>
</article>

How to get parent's parent's parent's id in Angularjs

I have a problem. I need to know how to get grandparent's ID in AngularJS.
I need "{{parent}}" to become "grand-parent".
(it should be <div id="me-and-my-grand-parent">)
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
var pid = document.getElementsByClassName("i-am-a-child");
var pid = this.parentNode.id;
if (this.parentNode&&this.parentNode.id)
var pid=this.parentNode.id;
$scope.parent = var pid;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
<div id="grand-parent{{$index}}" ng-repeat="item in items">
<div>
<div>
<div id="me-and-my-{{parent}}" class="i-am-a-child">
</div>
</div>
</div>
</div>
</div>
My actual code
<li ng-repeat="project in projects" ng-class="{active: project.childToggle, '': !project.childToggle,hasChild: project.children.length > 0 }" ng-dblclick="childToggleCt(project)" id="project-{{$index}}">
<div class="project-overview">
<header class="clearfix flip-area">
<span ng-if="!project.inCart" class="status dropdown-button warning pull-left" id="id-{{ParentIdShow}}" data-intro="Status bar" data-position="right">Pending</span>
And for now JS was like tis :
$scope.ParentIdShow = function(obj)
{
alert(obj.target.parentNode.parentNode.parentNode.id);
}
The answer to these types of "parent of my parent of my parent of my..." is to use the controller as syntax. Read more about it here. In short, it lets you do stuff like
<div ng-controller="ctrl1 as first">
<div ng-controller="ctrl2 as second">
...
<div ng-controller="ctrlN as Nth">
<div ng-repeat="i in arr">
{{first.property}}
{{second.otherProperty}}
{{Nth.nProperty}}
Note how you dont need any parent calls.

AngularJS nested ng-repeat

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>

Categories

Resources