<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@1.2.17" data-semver="1.2.17" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body ng-app="appName" ng-controller="TestCtrl">
    <p><button ng-click="addRow()">add row</button>
    <button ng-click="updateTableValue()">show table data</button></p>
    <table border="1">
        <thead>
          <tr>
            <th>checked</th>
            <th>row index</th>
            <th class="editableRow">first name</th>
            <th class="editableRow">last name</th>
            <th>delete row</th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="item in items">
            <td><input type="checkbox" ng-model="item.checked"></td><!-- チェックボックス -->
            <td>{{$index}}</td><!-- 行番号 -->
            <td cm-editable-text ng-model="item.firstName"></td><!-- 名前 -->
            <td cm-editable-text ng-model="item.lastName"></td><!-- 苗字 -->
            <td><button ng-click="removeRow($index)">delete</button></td><!-- 行削除ボタン -->
          </tr>
        </tbody>
    </table>
    <textarea>{{tableValue}}</textarea>
  </body>

</html>
// Code goes here
(function () {
    'use strict';
    angular.module('appName', function(){});
    angular.module('appName').controller('TestCtrl', function ($scope) {
        var exportTableValue = function() {
            var str = '';
            angular.forEach($scope.items, function(item) {
                str += '[' + new Date().getTime() + '] : ' + item.checked + ' : ' + item.firstName + ' ' + item.lastName + '\n';
            });
            $scope.tableValue = str;
        };
        $scope.addRow = function() {
            $scope.items.push( { checked:false, firstName:'firstName', lastName:'lastName' } );
        };
        $scope.removeRow = function(rowIndex) {
            $scope.items.splice(rowIndex, 1);
        };
        $scope.updateTableValue = function() {
            exportTableValue();
        };
        $scope.items = [
            { checked: false, firstName: 'Taiga',    lastName: 'Hirohata' },
            { checked: false, firstName: 'Eichi',    lastName: 'Arikawa'  },
            { checked: false, firstName: 'Masayuki', lastName: 'Tago'     }
        ];
        var collectionWatcher = $scope.$watchCollection('items', function(newCollection, oldCollection, scope) {
            exportTableValue();
        });
        $scope.$on('$destroy', function() {
            if(!angular.equals(collectionWatcher, null)) {
                collectionWatcher(); //モデル監視リスナーの解放
            }
        });
    });
    angular.module('appName').directive('cmEditableText', function () {
        return {
            restrict : 'A',
            require  : '^ngModel',
            link     : function(scope, element, attrs, ngModel) {

                ngModel.$render = function() {
                    element.html(ngModel.$viewValue);
                };

                element.on('dblclick', function() {

                    var clickTarget = angular.element(this);

                    var EDITING_PROP = 'editing';

                    if ( !clickTarget.hasClass(EDITING_PROP) ) {

                        clickTarget.addClass(EDITING_PROP);

                        clickTarget.html('<input type="text" value="' + ngModel.$viewValue + '" />');

                        var inputElement = clickTarget.children();

                        inputElement.on('focus', function() {
                            inputElement.on('blur', function() {
                                var inputValue = inputElement.val() || this.defaultValue;
                                clickTarget.removeClass(EDITING_PROP).text(inputValue);
                                inputElement.off();
                                scope.$apply(function() {
                                    ngModel.$setViewValue(inputValue);
                                });
                            });
                        });
                        inputElement[0].focus();
                    }

                });

                var destroyWatcher = scope.$on('$destroy', function () {
                    if ( angular.equals(destroyWatcher, null) ) {
                        return;
                    }
                    element.off();
                    destroyWatcher();
                    destroyWatcher = null;
                });
            }
        };
    });
}());
/* Styles go here */
th,td {
width: 100px;
}
input {
width: 97px;
}
textarea {
width: 500px;
height: 300px;
}
.editableRow {
color: red;
font-weight: bold;
}