Assigning a dynamic Angular ng-model in an ng-repeat block

I had an issue yesterday where within an ng-repeat construct, I needed to bind a model property to a form input.  But the property to bind would change depending on some criteria.  What started out as rather difficult, proved to actually be pretty simple when using nested controllers.

First I needed to create the main controller, and get my ng-repeat block working.

.controller('MainCtrl', function($scope) {
    $scope.list = [
      {type: 'car', make: 'Honda'},
      {type: 'car', make: 'BMW'},
      {type: 'car', make: 'Mercedes'},
      {type: 'car', make: 'Peupeot'},
      {type: 'animal', species: 'Cow'},
      {type: 'animal', species: 'Frog'},
      {type: 'animal', species: 'Crocodile'},
      {type: 'animal', species: 'Lion'},
    ];
  })
    <table class="table table-bordered table-condensed">
      <tr ng-repeat="item in list">
        <td>{{item.type}}</td>
        <td>
          <input type="text" class="form-control" />
        </td>
      </tr>
    </table>

What we  want to do now is have the input element bound to the ‘make’ property if the type is a ‘car’, and the ‘species’ property for animals.  In order to do that, we need to add a second controller that will be created for each ng-repeat scope, as shown below.  You will note that we switch on $scope.item.  Because we our ng-repeat statement was defined as “item in list”, we’ve defined a $scope property of item.  In the switch block, we then define a new property as $scope.name and assign the required value to it.

.controller('RepeatCtrl', function($scope) {
    
    switch( $scope.item.type ){
      case 'car':
        $scope.name = $scope.item.make;
        break;
        
      case 'animal':
        $scope.name = $scope.item.species;
        break;
    }
    
  })

Now we just need to reference the controller in the ng-repeat tag, and add the model to the input element.

    <table class="table table-bordered table-condensed">
      <tr ng-repeat="item in list" ng-controller="RepeatCtrl">
        <td>{{item.type}}</td>
        <td>
          <input type="text" class="form-control" ng-model="name" />
        </td>
      </tr>
    </table>

This will now give us what we want, in that the input will display the make or species, depending on the type.  The only problem however is that if we update the input, it’s only going to update  $scope.name, and not the value in the initial list array.  This is because we’re passing a value to $scope.name, not a reference (or pointer) to the initial object.  Don’t forget, in JavaScript, only arrays and objects are passed by reference, everything else is by value.

In order to get the behavior we want, let’s update the list, RepeatCtrl and html:

$scope.list = [
      {type: 'car', make: {name: 'Honda'}},
      {type: 'car', make: {name: 'BMW'}},
      {type: 'car', make: {name: 'Mercedes'}},
      {type: 'car', make: {name: 'Peupeot'}},
      {type: 'animal', species: {name: 'Cow'}},
      {type: 'animal', species: {name: 'Frog'}},
      {type: 'animal', species: {name: 'Crocodile'}},
      {type: 'animal', species: {name: 'Lion'}},
    ];
.controller('RepeatCtrl', function($scope) {
    
    switch( $scope.item.type ){
      case 'car':
        $scope.obj = $scope.item.make;
        break;
        
      case 'animal':
        $scope.obj = $scope.item.species;
        break;
    }
    
  })
    <table class="table table-bordered table-condensed">
      <tr ng-repeat="item in list" ng-controller="RepeatCtrl">
        <td>{{item.type}}</td>
        <td>
          <input type="text" class="form-control" ng-model="obj.name" />
        </td>
      </tr>
    </table>

 

And that’s it.  You can find a working example at  http://plnkr.co/edit/UVpgVv7OvSoai8vEnRB3?p=preview.

Leave a Reply

Your email address will not be published. Required fields are marked *