2

I am facing a problem with the binding of a radio button to the MVC model when using AngularJS, all other fields are binding successfully to the MVC model but the radio button is just not binding. Please advice where I got my wires mixed up.

HTML

<tr ng-repeat="item in experienceModel">
                <td><input type="text" name="StartDate" ng-model="item.StartDate | date:'dd-MM-yyyy'" id="date_teachingStart{{$index}}"/></td>
                <td><input type="text" name="EndDate" ng-model="item.EndDate | date:'dd-MM-yyyy'" id="date_teachingEnd{{$index}}"/></td>
                <td><input type="text" name="SubjectArea" ng-model="item.SubjectArea"/></td>
                <td><input type="text" name="Position" ng-model="item.Position"/></td>
                <td><input type="text" name="Institution" ng-model="item.Institution"/></td>
                <td><input type="text" name="City" ng-model="item.City"/></td>
                <td><input type="text" name="Country" ng-model="item.Country"/></td>
                <td class="centerAlign"><input type="radio" name="item.IsCurrent" value="{{$index}}" ng-model="selected.item" id="radio_experience[{{$index}}]" /></td>                    
                <td><a class="removeRow" title="Delete item" href="" ng-click="removeRow()"></a></td>
                <td><input type="hidden" name="CVId" ng-model="item.CVID" /></td>
                <td><input type="hidden" name="ExperienceID" ng-model="item.ExperienceID" /></td>
            </tr>

script

 var experienceModel = <%: Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(this.Model.TeachingExperience)) %>
     app.value("experienceModel", experienceModel);

Angular Controller

 app.controller('TeachingController', ['$scope', 'experienceModel',
function ($scope, experienceModel) {

        $scope.counter = 0;            

        $scope.selected = {};

        $scope.experienceModel = experienceModel;

        // Set the selected value to reflect initial data 
        $scope.experienceModel.forEach(function (item, index) {
            if (item.IsCurrent) {
                $scope.selected.item = index;
            }
        });

        if ($scope.experienceModel == null) {
            $scope.experienceModel = [{ 'ExperienceID': '', 'CVID': '', 'ExperienceCategoryId': '', 'StartDate': '', 'EndDate': '', 'SubjectArea': '', 'NoOfDays': '', 'Position': '', 'Programme': '', 'KnowledgeArea': '', 'Institution': '', 'Client': '', 'City': '', 'Country': '', 'IsCurrent': '' }];
        }

        if ($scope.experienceModel.length == 0) {
            $scope.experienceModel.push({ 'ExperienceID': '', 'CVID': '', 'ExperienceCategoryId': '', 'StartDate': '', 'EndDate': '', 'SubjectArea': '', 'NoOfDays': '', 'Position': '', 'Programme': '', 'KnowledgeArea': '', 'Institution': '', 'Client': '', 'City': '', 'Country': '', 'IsCurrent': '' });
        }

        $scope.$watch('selected.item', function (index) {
            $scope.items.forEach(function (item, i) {
                item.isCurrent = i === parseInt(index);
            });
        });

        $scope.counter = 1;

    $scope.addRow = function () {
        $scope.experienceModel.push({ 'ExperienceID': '', 'CVID': '', 'ExperienceCategoryId': '', 'StartDate': '', 'EndDate': '', 'SubjectArea': '', 'NoOfDays': '', 'Position': '', 'Programme': '', 'KnowledgeArea': '', 'Institution': '', 'Client': '', 'City': '', 'Country': '', 'IsCurrent': '' });
        $scope.counter++;
    }
    $scope.removeRow = function () {
        var row = $(this);
        $scope.experienceModel.splice(row[0].$index, 1);
        $scope.counter--;
    }
}]);
2

1 Answer 1

2

"The thing" that's responsible for two-way data binding in AngularJS is ng-model. You should set this attribute on all form fields that you want the user to interact with.

It will take care of setting fields' values for you, so to speak - so the value attributes should be removed. You can have ng-model or value but if you do decide to only set the value (it makes sense in some cases, e.g. readonly fields), it will be bound in one direction only - it will react to changes to the model but not be able to change the model.

Ok, after this theoretical introduction, here's a possible solution:

angular.module('app', [])
  .controller('ctrl', function($scope) {

    // Sample data
    $scope.items = [
      {name: 'First', isCurrent: false},
      {name: 'Second', isCurrent: false},
      {name: 'Third', isCurrent: false},
      {name: 'Fourth', isCurrent: false}
    ];

    /* Object to track selected things. (It's better to use objects
     * rather than plain variables to avoid problems with variable's scope) */
    $scope.selected = {};

    // Set the selected value to reflect initial data 
    $scope.items.forEach(function(item, index) {
      if (item.isCurrent) {
        $scope.selected.item = index;
      }
    });

    /* If selected item changes, set corresponding item's 
     * isCurrent property to true and all others to false */
    $scope.$watch('selected.item', function(index) {
      $scope.items.forEach(function(item, i) {
        item.isCurrent = i === parseInt(index);
      });
    });

  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<body ng-app="app" ng-controller="ctrl">
  <ul>
    <li ng-repeat="item in items">
      {{ item.name }}
      <label>
        <input type="radio" ng-model="selected.item" value="{{ $index }}">current
      </label>
    </li>
  </ul>
  <pre>{{ items | json }}</pre>
</body>

Ok, so after merging this and things we've clarified in comments, your html code should look like this:

<!-- (...) -->
<td><input type="text" name="city" ng-model="item.City" /></td>
<td><input type="text" name="country" ng-model="item.Country" /></td>
<td class="centerAlign"><input type="radio" ng-model="selected.item" /></td>
<td><a class="removeRow" title="Delete item" href="" ng-click="removeRow($index)"></a></td>
<!-- (...) -->

Mind some "bonus" improvements:

  • you don't need to use href="javascript:void(0)" when working with AngularJS. href="" will be sufficient (https://docs.angularjs.org/api/ng/directive/a)
  • when removing a row it's easiest to just pass iteration's $index to your removeRow() function
  • if you're not using Angular's validation, you won't need name attributes
  • if you're not using labels (in particular <label for="sth">) you won't nee id attributes
Sign up to request clarification or add additional context in comments.

5 Comments

What would my controller and html then look like? Should I do a foreach in the controller and change my html to. 'ng-model="experienceModel{{$index}}.IsCurrent" '
@Righardt I've updated the answer with working example (you can run it here to see how it works). Btw. in your ng-repeat you don't need to reference certain items as experienceModel[{{$index}}] you can use the iteration variable you've chosen and Angular will understand that. So for example in your code instead of name="TeachingExperience[{{$index}}].StartDate" you can write name="item.StartDate". Much easier for the eyes :)
thanks, I have changed to name=item.StartDate, How should I implement ng-model in the controller and in my HTML?
I've updated my answer with example of changed html code. As for the controller, you can leave it pretty much as it was, only add the logic responsible for this selected.item (so the part with creating selected object, $scope.$watching of its change, and iterating over your experienceModel array to set the initial selected.item value. And of course the $scope.items from my example is the same as your $scope.experienceModel. That's it.
Please see the updated changes in my code. The values are pulling thru and displaying correctly, however it seems that the two-way binding is not working. The experience model in the MVC controller is null.

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.