idiomatic way of having models and looped forms in angularjs

If I'm building an index page for a blog in AngularJS with comments like this:

<li ng-repeat="post in posts">
    <h2>{{post.title}}
    <p>{{post.description}}</p>
    <h3>Leave a comment</h3>
    <form ng-submit="postComment()">
       <input type="hidden" value="{{post.id}}">
       Name: <input type="text"> <br>
       Comment: <textarea></textarea> <br>
       <input type="submit" value="Post comment">
    </form>
</li>

I can't figure out what the right way is to associate a model with the form to access from the controller's postComment() function. If you use just one model for all the forms, then starting to leave a comment on one post will start to leave a comment on all of them—unless you do some weird stuff with the hidden post.id field, but that starts to feel unidiomatic.

You can pass an object into the function call.

<form ng-submit="postComment(post.id)">

As far as the other inputs, it really depends on the situation, but in this case, the input and textarea is inside an ngRepeat, which creates new scope. For that reason, assigning to a value on the scope will not affect other elements on the page.

<li ng-repeat="post in posts">
    <h2>{{post.title}}
    <p>{{post.description}}</p>
    <h3>Leave a comment</h3>
    <form ng-submit="postComment(post.id, commentName, commentText)">
       Name: <input type="text" ng-model="commentName"> <br>
       Comment: <textarea ng-model="commentText"></textarea> <br>
       <input type="submit" value="Post comment">
    </form>
</li>

is there any way of taking advantage of the two way communication there? (e.g. add an errmsg field from an AJAX call done in postComment or clear the form on submit?)

I suggest a directive with its own controller. With this approach, the comment model is encapsulated in the directive. The directive needs two pieces of information: 1) the postID 2) what function to call to save the comment. To support showing an error message returned from the server, a callback function is passed along with the comment. This allows the controller to feed the directive any error messages or any other information it might need after a save operation attempt.

app.directive('commentForm', function() {
  var template = '<form name="commentForm" '
    + 'ng-submit="save({comment: comment, callbackFn: callbackFn})">'
    + '<h3>Leave a comment</h3>'
    + 'Name: <input type="text" ng-model="comment.name" name="commentName"> <br>'
    + '<span class="error" ng-show="commentForm.commentName.$error.myError">Error</span><br>'
    + 'Comment: <textarea ng-model="comment.text"></textarea> <br>'
    + '<input type="submit" value="Save comment">'
    + '</form>';
  return {
    restrict: 'E',
    template: template,
    scope: { postId: '@', save: '&' },
    controller: function($scope) {
      $scope.callbackFn = function(args) {
        console.log('callback', args);
        if(args.error.name) {
          $scope.commentForm.commentName.$setValidity("myError", false);
        } else {
          // clear form
          $scope.comment.name = '';
          $scope.comment.text = '';
        }
      };
    }
  };
});


app.controller('MainCtrl', function($scope, $timeout) {
  $scope.post = {id: 1};
  $scope.saveComment = function(comment, callbackFn) {
    console.log('saving...', comment);
    // Call $http here... then call the callback.
    // We'll simulate doing something with a timeout.
    $timeout(function() {
      if(comment.name === "name") {
        callbackFn( {error: {name: 'try again'}} );
      } else {
        callbackFn( {error: {}} );
      }
   }, 1500)
  }
});

Use as follows:

<comment-form post-id="{{post.id}}" save="saveComment(comment, callbackFn)"></comment-form>

Note the somewhat odd syntax related to the '&' syntax: when the directive is used in the HTML, you specify arguments for the saveComment() function. In the directive/template, an object/map is used to associate each argument name with its value. The value is a local/directive scope property.

Plnkr. In the plnkr I simulated an AJAX post using a $timeout of 1.5 seconds. If you enter name in the name textbox and click the save button, you'll get an error in 1.5 seconds. Otherwise the form clears after 1.5 seconds.

Depending on how far you want to take this... you could encapsulate your post template into a directive too:

<li ng-repeat="post in posts">
   <post=post></post>
   <comment-form ...></comment-form>
</li>

You could also put the comment-form inside the post directive template, simplifying the HTML even further:

<li ng-repeat="post in posts">
   <post=post></post>
</li>

You could also have a post-list directive, and its template would contain the ng-repeat, reducing the HTML to a single element:

<post-list posts=posts></post-list>

I personally haven't decided how far one should go with custom directives yet. I would be very interested in any comments people have about this.