How to validate table row data with Angular?

All we need is an easy explanation of the problem, so here it is.

I have table with ng-repeat for table rows. I want to make inline editing and validation of data from single row, but it is impossible to use form or ng-form inside table.
For inline editing I use inputs with ng-show inside td.
So, I have two questions:

  1. Is it correct approach for inline editing? (using hidden inputs
    inside td).

  2. How can I validate data from row?

Update: I want for table row something like “form submitting” and add error class for table cells with wrong data.

I’m new to angular.

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

NG-Form works on elements that are not a HTML form. So, you should be able to use the built ng-form validations inside a table. It seems to track the forms properly per row for me.

https://docs.angularjs.org/api/ng/directive/form

 <tr ng-repeat="market in markets | orderBy:'name'" ng-form name="myForm">
   <td>{{market.id}}</td>
   <td ng-class="{'has-error': !myForm.minimum.$valid}">
     <input type="number" name="minimum" min="0" max="10000" ng-model="market.minimum" />
   </td>
   <td ng-class="{'has-error': !myForm.cash.$valid}">
    <input type="number" ng-model="market.cash" min="0" name="cash" />
  </td>
  <td>
    <input type="submit" ng-disabled="!myForm.$valid" ng-click="save(market)"/>
  </td>
</tr>

Method 2

I create example with simple validation to your second question:

View:

<div ng-controller="MyCtrl">
    <table class="table table-condensed">
        <thead>
            <tr>
                <th>Id</th>
                <th>Firstname</th>
                <th>Lastname</th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="item in items">
                <td>{{item.id}}</td>
                <td><input ng-model="item.firstName" ng-class="{ error: !item.firstName }"/></td>
                <td><input ng-model="item.lastName" ng-class="{ error: !item.lastName }"/></td>
                <td><input ng-model="item.email" ng-pattern="emailRegExp" ng-class="{ error: !item.email }"/></td>
                <td><button ng-disabled="!item.lastName || !item.firstName || !item.email"/>Submit</td>
            </tr>
        </tbody>
    </table>
</div>

Controller:

function MyCtrl($scope) {
    $scope.items = [
        {
            id: 1,
            firstName: 'Ivan',
            lastName: 'Ivanov',
            email: '[email protected]'
        },
        {
            id: 2,
            firstName: 'Petr',
            lastName: 'Petrov',
            email: '[email protected]'
        }
    ];

    $scope.emailRegExp = /^[A-Za-z0-9._%+-][email protected][A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/; 
}

Please, see jsfiddle ๐Ÿ™‚

Jsfiddle

For your question in comment about date validation:

I see two ways for do it:

1) In yor controller you create $scope.dateRegExp = "/^\d{2}([./-])\d{2}\1\d{4}$/" and into the view you using it with ng-pattern="dateRegExp"

2) You can use ng-change="" directive:

View:

<tr ng-repeat="item in items">
    <td><input ng-model="item.date" ng-change="validateDate(itemDate)" ng-class="{ error: dateInputError }"/></td>

Controller:

$scope.dateInputError = false;

$scope.validateDate = function(date) {
   if(//some validation){
      $scope.dateInputError = true; //true - it means error style shows
   }
};

Method 3

Approaches for this problem (incremental):

  1. (optional) Don’t use a table. use nested lists and force it with css to look like a table.

  2. Do it in the Angularish way: use a custom directive on the table cells/rows (whatever) the directive should watch the cell content for changes and then run a any custom validation and logic you have.

Method 4

  1. I don’t think you need to hide your inputs in tds, you could just use CSS to make them fill the td, with no border for example.

  2. As @alonisser said, the angular-way is to create a directive to handle that. To solve my similar case, I created a ‘super’ table directive which provide my table template and handle its behavior. About the template: I wrapped the table into a form ๐Ÿ˜‰

HTML example:

<form name="tableform">
    <table>
      <tbody>
        <tr class="tr-rawdata" ng-repeat="row in tableData track by $index" ng-model="row">
          <td ng-repeat="cell in row track by $index">
            <input type="text" ng-model="row[$index]">
          </td>
        </tr>
      </tbody>
    </table>
</form>

directive example:

angular.module('myApp').directive('superTable', function() {
  return {
    restrict: 'A',
    templateUrl: 'partials/super-table.html',
    link: function($scope, $elem, $attrs) {

      $elem.on('blur', function(e) {
        //do something
      });
    }
  }
});

Note: Use and implement method 1 because this method fully tested our system.
Thank you ๐Ÿ™‚

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply