1

New to AngularJS and have question on how to use the ng-repeat.

Currently I can only find examples where you read from a service, and then ng-repeat loops over that.

My situation is a admin can create an invitation for guests and set a property called:

total_in_party = 4;

Now based off of "total_in_party" I want to 3 input fields, for each guest in the party

Like:

<div ng-repeat="guest in party">
  <input type="text" ng-model="guest.first_name" />
  <input type="text" ng-model="guest.last_name" />
  <select ng-model="selectedGuestMeal" ng-options="meal.id as meal.name for meal in meals"></select>
</div>

For this case, it should print those 3 input fields 4 times.

I think I am close, just need to know how to create the party object if I have no data stored in it yet?

If I am doing this completely wrong - don't hesitate to inform me!

3 Answers 3

3

I had to solve this exact problem recently. You essentially need to create an array of guest objects in your scope and then bind the array to a form using ng-repeat.

See a working demo of my solution here: http://plnkr.co/edit/2aYSLYe0IcRGXR7Lm0HR?p=preview

app.controller('MainCtrl', function($scope) {
  $scope.numberOfGuests = 1;
  $scope.guests = [];

  addGuests($scope.numberOfGuests);

  // When the number of guests changes, we want to repopulate the
  // array of guest info (in a non-destructive manner).
  $scope.$watch('numberOfGuests', function (newValue, oldValue) {
    if (!newValue) {
      return;
    }

    newValue = parseInt(newValue, 10);
    oldValue = parseInt(oldValue, 10);

    // If the number of guests increased, add some empty objects to the array.
    if (!isNaN(oldValue) && newValue > oldValue) {
      var numberOfGuests = newValue - oldValue;
      addGuests(numberOfGuests);
    } else {
      // Otherwise reset length of array
      $scope.guests.length = newValue;
    }
  });

  function addGuests(numberToAdd) {
    if (!isNaN(numberToAdd) && numberToAdd > 0) {
      for (var i = 0; i < numberToAdd; i++) {
        $scope.guests.push({});
      }
    }
  }
});

Here's the view

<body ng-controller="MainCtrl">
  <form>
    <p>Nunmber of Guests <input type="number" ng-model="numberOfGuests" ></p>
    <table>
      <tr ng-repeat="guest in guests track by $index">
        <td>{{$index + 1}}</td>
        <td><input type="text" ng-model="guest.name"></td>
        <td><input type="text" ng-model="guest.email"></td>
      </tr>
    </table>
  </form>
  <pre>{{guests | json}}</pre>
</body>
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you! Although this does more than my current needs it's going to be very helpful going forward!
I'm curios why you use $scope.$watch instead of an onchange event. It seems more intuitive to me to be able to look at the HTML and see an onchange attached than to notice a $watch in the controller. I'm still quite new to Angular, so please let me know if I'm missing something or if it's just a matter of preference.
@Darryl the ng-change directive will only fire when the onchange event for that html element is fired, not when the underlying model changes. I wanted my solution to work regardless of how numberOfGuests was modified. See this example: plnkr.co/edit/ECZghdmvuHcQNi35Qmol?p=preview
1

You can instantiate a guests property on $scope, in the controller based on total_in_party;

function initGuests(totalInParty) {
  $scope.guests = [];
  for (var i = 0; i < totalInParty; i++) {
    $scope.guests[i] = {
      first_name: '',
      last_name: '',
      meal: ''
    };
  }
}

in your ng-repeat make use of $index, like this:

<div ng-repeat="guest in guests">
  <input type="text" ng-model="guests[$index].first_name" />
  <input type="text" ng-model="guests[$index].last_name" />
  <select ng-model="guests[$index].meal" ng-options="meal.id as meal.name for meal in meals"></select>
</div>

Did not tested it, but it should work :)

3 Comments

Thanks Tamas, exactly what I needed! Can you give me a breakdown on whats going on with the $index? I am going to be saving each first_name last_name and meal as it's own row.
$index is simply an iterator, for each step in the repeat it is incremented automatically: docs.angularjs.org/api/ng.directive:ngRepeat.
Perfect, I just found that link!
1

You can watch the total_in_party number and add/subtract guests based off of it

JS

$scope.$watch('total_in_party', function(newValue, oldValue) {
  if (newValue != null) {
    console.log(newValue, oldValue);
    if (newValue > oldValue) { //add
      var i = newValue - oldValue;
      for (var k = 0; k < i; k++) {
        $scope.party.push({
          first_name: "",
          last_name: "",
          selectedGuestMeal: null
        });
      }
    } else if (newValue < oldValue) { //subtract
      var i = oldValue - newValue;
      $scope.party.splice(0, i);
    }
  }
});

see this plunkr for example

3 Comments

this solution will also work alongside an init function, e.g. the one @tamas-pap suggested. Where is your initial total_in_party value coming from? The server? A http call? Or do you have it in your view on page load?
An admin creates a invitation specifying the total_in_party which is just the sum of (invitee + guests), an association is made between the invitation on the invitee. I am now working on the invitee end - where the invitee logs in, and based on total_in_party I create Name/Meal/RSVP fields for them & their guests. I have this hooked up to a ROR install, so I am receiving all my data through JSON.
Ok, either way, when total_in_party comes from the server, you can set that value on your controller and angular should add the correct amount of fields for you using my $watch above

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.