angular.module('myapp', [])

.controller('ctrl1', ['$scope', '$http','$p','$mock', function($scope, $http, $p, $mock) {
  $scope.init = function() {
    console.clear();
    $p.ajax($mock.data1,1000).then(function(d){
      $scope.config1.t1.init(d);  
    },function(d){
      $scope.config1.t1.timeout(d);
    });
    
    $p.ajax($mock.data2,500).then(function(d){
      $scope.config2.t1.init(d);
    });
  }
  
  $scope.refreshPage = function(){
    $scope.init();
  }


  $scope.config1 = {
    t1:{
      subgrid:true,
      width:300, 
      height:200,
      config:[
        {
          title:"Filed 1",
          map:"field1"
        },
        {
          title:"Filed 2",
          map:"field2"
        },
        {
          title:"Filed 3",
          map:"field3"
        },
        {
          title:"Filed 4",
          map:"field4"
        },
        {
          title:"Filed 5",
          map:"field5"
        },
        {
          title:"Filed 6",
          map:"field6"
        }
        ],
        t:"",
        load:function(id, idx){
          $p.ajax($mock.data1,200).then(function(d){
            $scope.config1.t2.init(d); 
          },function(d){
            $scope.config1.t2.timeout(d);
          });
        }
        
      },
    t2: {
      subgrid:true,
      width:200, 
      height:100,
      config:[
      {
        title:"Filed 1",
        map:"field1"
      },
      {
        title:"Filed 2",
        map:"field2"
      }
      ],
      t:"",
      load:function(id, idx){
        $p.ajax($mock.data1,200).then(function(d){
          $scope.config1.t3.init(d); 
        },function(d){
            $scope.config1.t3.timeout(d);
          });
      }
    },
    t3: {
      subgrid:false,
      width:200, 
      height:100,
      config:[
      {
        title:"Filed 1",
        map:"field1"
      },
      {
        title:"Filed 2",
        map:"field2"
      }
      ],
      t:""
    }
  };


  $scope.config2 = {
    t1:{
      subgrid:true,
      width:300, 
      height:200,
      config:[
        {
          title:"Filed 1",
          map:"field1"
        },
        {
          title:"Filed 2",
          map:"field2"
        },
        {
          title:"Filed 3",
          map:"field3"
        },
        {
          title:"Filed 4",
          map:"field4"
        },
        {
          title:"Filed 5",
          map:"field5"
        },
        {
          title:"Filed 6",
          map:"field6"
        }
        ],
        t:"",
        load:function(id, idx){
          $p.eajax($mock.data1,200).then(function(d){
            $scope.config2.t2.init(d); 
        },function(d){
            $scope.config2.t2.timeout(d);
          });
      }
    },
    t2: {
      subgrid:false,
      width:200, 
      height:100,
      config:[
      {
        title:"Filed 1",
        map:"field1"
      },
      {
        title:"Filed 2",
        map:"field2"
      }
      ],
      t:"",
      load:function(id, idx){
        $p.ajax($mock.data1,200).then(function(d){
          $scope.config1.t3.init(d); 
        },function(d){
            $scope.config1.t3.timeout(d);
          });
      }
    }
  };
}]);



























<!DOCTYPE html>
<html>

  <head>
      <link rel="stylesheet" href="style.css">
      <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
      <script src="controller.js"></script>
      <script src="subgrid.js"></script>
  </head>

  <body ng-app="myapp">
 
      <div ng-controller="ctrl1" ng-init="init()">
    
          <subgrid config="config1"> </subgrid>
  
          <hr>
          
          <subgrid config="config2"> </subgrid>
 
         <button ng-click="refreshPage()">refresh</button> 
      </div>

  </body>

</html>
angular.module('myapp')
  .directive('subgrid', ['$timeout','$compile',function($timeout,$compile) {
    return {
      restrict: 'E',
      scope: {
        config: '=',
        count: '='
      },
      
      templateUrl: 'subgrid.html',
      link: function(scope, elem, attr, ngModelCtrl) {
          scope.endrender=function(){
            $timeout(function(){
              scope.render = false;
            },1);
          }
          scope.expanded = false;
          scope.expandedid = null;
          scope.cnt = scope.count?scope.count:1;
          scope.cnf = scope.config["t"+scope.cnt];
          scope.guid = guid();
          scope.$watch('cnf.t',function() {
              scope.render = true;
          }, true);
          scope.cnf.timeout = function(error){
              scope.cnf.subgrid = false;
              scope.cnf.config = [{title:"Message",map:"field1"}];
              scope.cnf.t = {RowCount:1, field1:[error],index:[1]};
          }
          scope.cnf.init = function(d){
            scope.cnf.t = "";
            $timeout(function(){
              scope.cnf.t = d;
            },1);
          }
          
          scope.expander = function(id, idx){
            //if not same row
            if(scope.cnf.subgrid)
            if(id!==scope.expandedid){
                angular.element(elem[0].querySelector("#"+scope.expandedid)).children().eq(0).children().text("+");
                angular.element(elem[0].querySelector("#"+scope.guid+'sub')).remove();
                scope.expandedid = id;
                var count = scope.cnt + 1;
                var tr = angular.element(elem[0].querySelector("#"+id));
                tr.children().eq(0).children().text("-");
                var exid = scope.guid+'sub';
                tr.after($compile("<tr id='"+exid+"'><td colspan ='{{cnf.config.length+1}}' style='padding:10px;'><subgrid count='"+count +"' config='config'></subgrid></td></tr>")(scope));
                if (typeof scope.cnf.load === "function") { 
                    scope.config["t"+count].t = "";
                    scope.cnf.load(id, idx);
                }
                scope.expanded = true;
            }
            else{
              if(scope.expanded){
                angular.element(elem[0].querySelector("#"+id)).children().eq(0).children().text("+");
                angular.element(elem[0].querySelector("#"+scope.guid+'sub')).remove();
                scope.expanded = false;
                scope.expandedid = null;
              }
              else{
                scope.expanded = true;
                scope.expandedid = id;
                var count = scope.cnt + 1;
                var tr = angular.element(elem[0].querySelector("#"+id));
                tr.children().eq(0).children().text("-");
                var exid = scope.guid+'sub';
                tr.after($compile("<tr id='"+exid+"'><td colspan ='{{cnf.config.length+1}}' style='padding:10px;'><subgrid count='"+count +"' config='config'></subgrid></td></tr>")(scope));
                if (typeof scope.cnf.load === "function") { 
                    scope.config["t"+count].t = "";
                    scope.cnf.load(id, idx);
                }
              }
            }
          }
          
          function guid() {
              function s4() {
                return Math.floor((1 + Math.random()) * 0x10000)
                  .toString(16)
                  .substring(1);
              }
              return "id"+s4() + s4();
              
          }

      }
    };
  }])

.service('$mock',[function() {
    this.data1 = {
        RowCount:5,
        field1:["raz","dva","tri","cht","pyat"],
        field2:[1,2,3,4,5],
        field3:["sh","sem","vos","dev","des"],
        field4:[4,5,4,1,4],
        field5:[true,false,true,true,false],
        field6:["sdfdsf","sdfdsfdsf","rtyjyjtyj","wrtwrt","wrtw"],
        index:[1,2,3,4,5]
      };
      
    this.data2 = {
        RowCount:5,
        field1:["sd","dfg","iopu","ndw","bfj"],
        field2:[1,2,3,4,5],
        field3:["btr","ekhg","fs","tmvc","soigb"],
        field4:[4,5,4,1,4],
        field5:[true,false,true,true,false],
        field6:["sdfdsf","sdfdsfdsf","rtyjyjtyj","wrtwrt","wrtw"],
        index:[1,2,3,4,5]
      };
    
}])

.service('$p',['$q',function($q) {
    this.ajax = function (x,timeout) {
        return async(x,timeout);
    }
    
    this.eajax = function (x,timeout) {
        return easync(x,timeout);
    }
    var async = function(name,timeout) {
        var deferred = $q.defer();
        setTimeout(function() {
          //deferred.notify('About to greet ' + name + '.');
          deferred.resolve(name);
          //deferred.reject('Greeting ' + name + ' is not allowed.');
        }, timeout?timeout:1000);
        return deferred.promise;
      }
      
      var easync = function(name,timeout) {
        var deferred = $q.defer();
        setTimeout(function() {
          //deferred.notify('About to greet ' + name + '.');
          //deferred.resolve(name);
          deferred.reject('api error');
        }, timeout?timeout:1000);
        return deferred.promise;
      }
    
}]);
<div class="t-datasheet" ng-class="{'spinner':render}" ng-style="{'width':cnf.width+'px','height':cnf.height+'px'}">
<table ng-hide="render">
  <thead >
    <tr>
        <td>#</td>
        <td  ng-repeat="c in cnf.config" ng-cloak>{{c.title}}</td>
    </tr>
  </thead>
  <tbody >
 
   <tr id="{{guid+i}}" ng-repeat="i in cnf.t.index" ng-init="($last && endrender())">
       <td ng-click="expander(guid+i,i)" ><span ng-show="cnf.subgrid">+</span></td>
       <td ng-repeat="c in cnf.config" ng-cloak>{{cnf.t[c.map][i-1]}}</td>
    </tr>
  </tbody>
</table>
</div>
/* Styles go here */
.t-datasheet{
  border: 1px solid #e4e4e4;
  padding:2px;
  overflow:scroll;
}

.t-datasheet table{
  border-collapse:collapse;
  font-family:Tahoma;
  font-size:9pt;
  table-layout:fixed;
}

.t-datasheet table td{
  border:1px solid #000;
}

.spinner{
    background: url(https://stanfy.com/wp-content/uploads/2015/09/1-V3h-VWthi5lL0QySF6qZPw.gif) center center no-repeat;
    background-size:40px 40px;
}