AngularJS ng-repeat with custom element inside a table is rendering strangely

46,459

Solution 1

<td> is known to behave strangely in directives like this. Instead, use a directive on the parent <tr>. Read more about this issue here: https://github.com/angular/angular.js/issues/1459

<table>
    <tr ng-repeat="p in people" my-element></tr>
</table>

Here is how you can further improve your directive so that it is more re-usable.

app.directive('myElement', function () {
  return {
    scope: {
      item: '=myElement'
    },
    restrict: 'EA',
    template: '<td>Name: {{item.name}}</td><td>Age: {{item.age}}</td>'
    };
});

and pass in the value of item like so:

  <table>
    <tr ng-repeat="person in people" my-element="person"></tr>
  </table>

Live Demo

Solution 2

Apply the directive to <tr> like this:

<table class="table table-hover">
    <tr my-element blah='p' ng-repeat="p in people"></tr>
</table>

app.directive('myElement', function () {
    return {
        restrict: 'A',
        scope:{
            ngModel: '=blah'
        },
        template: '<td>Name: {{ ngModel.name }}</td><td>Age: {{ ngModel.age }}</td>'
    }
});

Working Demo

Solution 3

Use replace: true in your directive and your <my-element> will be replaced with the root item in your template, a <td>, so this will not confuse the HTML.

Share:
46,459
ravishi
Author by

ravishi

Updated on January 20, 2020

Comments

  • ravishi
    ravishi over 4 years

    I'm trying to re-use a portion of my HTML view in multiple places. The portion I want to re-use is table cells in an HTML table. The problem is that my custom directive inside a ng-repeat is doing funny things. I have reproduced the problem on jsFiddle. There are two HTML tables in the jsFiddle. The first is ng-repeat with the table cells written in the view and the second is the table cells coming from a directive, my-element. Chrome dev tools report that the rendered HTML looks like this. Note that the custom element appears only once and is outside the table.

    Rendered HTML

    <div ng-controller="MyCtrl" class="ng-scope">
        table1
        <table class="table table-hover">
          <tbody><!-- ngRepeat: p in people -->
              <tr ng-repeat="p in people" class="ng-scope">
                <td class="ng-binding">Name: Mike</td>
                <td class="ng-binding">Age: 20</td>
              </tr>
              <tr ng-repeat="p in people" class="ng-scope">
                <td class="ng-binding">Name: Peter S</td>
                <td class="ng-binding">Age: 22</td>
              </tr>
          </tbody>
        </table>
        <br>table2
        <my-element class="ng-binding">Name: Age: </my-element>
        <table class="table table-hover">
          <tbody>
            <!-- ngRepeat: p in people -->
            <tr ng-repeat="p in people" class="ng-scope">
            </tr>
            <tr ng-repeat="p in people" class="ng-scope">    
            </tr>
          </tbody>
        </table>
    </div>
    

    Source HTML

    <div ng-controller="MyCtrl">
        table1
        <table class="table table-hover">
            <tr ng-repeat="p in people">
                <td>Name: {{ p.name }}</td>
                <td>Age: {{ p.age }}</td>
            </tr>
        </table>
        <br/>table2
        <table class="table table-hover">
            <tr ng-repeat="p in people">
                <my-element></my-element>
            </tr>
        </table>
    </div>
    

    Source JS

    var app = angular.module('myApp', []);
    
    app.directive('myElement', function () {
        return {
            restrict: 'E',
            template: '<td>Name: {{ p.name }}</td><td>Age: {{ p.age }}</td>'
        }
    });
    
    function MyCtrl($scope) {
        $scope.people = [{
            name: 'Mike',
            age: 20
        }, {
            name: 'Peter S',
            age: 22
        }];
    }
    

    Please note the jsFiddle is a trivial example and common sense would lead to just not using directives at all. However, my target code has a much larger template that I want to re-use. I've tried using "ng-include" as well but the result is similar.

  • Yoshi
    Yoshi over 10 years
    with the current template this would result in an Template must have exactly one root element. error.
  • ravishi
    ravishi over 10 years
    Both m59 and @sza answers worked but I appreciated the additional comments about code improvements.
  • boneskull
    boneskull over 10 years
    Ah, yes of course. Maybe wrap in a <tbody> if that works within the context of a <tr>.. otherwise see m59's answer.
  • chander
    chander over 10 years
    I'd avoid this - re-using ng-model seems to unnecessarily confuse things with the normal use of ng-model and bindings with forms, etc.
  • zs2020
    zs2020 over 10 years
    @TheBigC I think I'd better rename it to something else. Thx.
  • m59
    m59 over 10 years
    Good idea, but seems to throw that template error no matter what. Not sure how to fix that.
  • zs2020
    zs2020 over 10 years
    What is so special about this solution then?
  • m59
    m59 over 10 years
    Apologies. The naming made your intent unclear. I thought you were doing something else.
  • zs2020
    zs2020 over 10 years
    @TheBigC This one should not matter at all.
  • ade jones
    ade jones over 9 years
    this works, but the rendered html loses the internal td tags. e.g. <td my-element="" class="ng-binding">Name: MikeAge: 20</td> with nothing between Mike & Age. Any idea why?
  • gfaceless
    gfaceless over 9 years
    This seems to work, but as @ade jones pointed out, it actually fails. should either follow zsong's answer or use 'replace: true'. Further information about 'replace: true': github.com/angular/angular.js/issues/1459
  • m59
    m59 over 9 years
    @gfaceless ah yeah, that was a mess. I didn't notice what I had done there. I corrected everything. I was new to Angular back then.
  • Chris Hayes
    Chris Hayes over 8 years
    is there a way to access $index in the template?
  • m59
    m59 over 8 years
    @ChrisHayes Sure, pass it in on the scope. index="$index" scope: {index: '=',