0

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>

JSON format example

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.

Trevor Bye
  • 686
  • 1
  • 6
  • 26

3 Answers3

2

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

Jeffrey Patterson
  • 2,342
  • 1
  • 13
  • 9
  • Thanks for your answer, I will start working with this and see where it leads. Do you expect my original attempt using `$scope.x.nodeList.push(node);` to push into the nested array to work, or is there a different implementation you would suggest that would work better with the code you supplied? – Trevor Bye Nov 03 '16 at 15:22
  • Check the update I just posted. If you pass the parent to the comment function, you can add the child comment directly to it's parent. This will cause a digest cycle and your ui will update. There's a plunker if you want to see it in action. – Jeffrey Patterson Nov 03 '16 at 15:46
  • I've updated the answer to include the $http post call. – Jeffrey Patterson Nov 03 '16 at 19:56
  • Great thanks for giving high-detail. I may not get to this until tomorrow so I will let you know how this works. – Trevor Bye Nov 03 '16 at 21:28
  • Worked like a charm, and I learned a good amount from your answer. Thanks! – Trevor Bye Nov 05 '16 at 00:53
0

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.

Vinny
  • 449
  • 3
  • 6
  • What do you mean by checking bindings? Also, when i do `$scope.post.nodeList.push(node);` it pushes it in without refreshing, but when I try and do what you suggested within the ng-include nested ng-repeats, it won't work. – Trevor Bye Nov 02 '16 at 17:56
  • Looked in the console and I'm getting "TypeError: Cannot read property 'x' of undefined" on this logic: `vm.$scope.x.nodeList.push(node);` – Trevor Bye Nov 02 '16 at 18:14
  • Try setting var vm = $scope. I don't think my solution is working regardless. Starting to think you're losing the reference to nodeList somehow. http://stackoverflow.com/questions/29849293/ng-repeat-not-updating-on-update-of-array – Vinny Nov 02 '16 at 18:22
  • So based on the answer in that question what I'm thinking of trying is modifying `ng-repeat="x in x.nodeList"` to `ng-repeat="x in vm.x.nodeList"`, or that's at least my interpretation of this. – Trevor Bye Nov 02 '16 at 20:02
  • if you start using this `var vm = this;`then you need to use `controller as `syntax – ngCoder Nov 03 '16 at 06:10
0

Try using $scope.x.nodeList.concat([node]); instead of pushing the value, like this avoid mutations in the object.

Alejandro Garcia Anglada
  • 2,373
  • 1
  • 25
  • 41