0

[Edited to reflect comments in @shaunhusain answer]

I have a nested ng-repeat construct for which I want to instantiate a new instance of a directive for every inner item, passing to that directive the data from that inner item. In the code below, I've included two nested loops. The one that works uses one-way binding via the {{ }} notation, and the other appears to work as well...until you hit the Refresh button. Even though $scope.frames changes (and the {{ }} binding continues to reflect the changes), the binding for the directive is not triggered.

What am I missing?

var myApp = angular.module('myApp', []);

myApp.directive("myDirective", function () {
    return {
        restrict: 'E',
        scope: {
            boundData: '=data'
        },
        link: function (scope, element) {
           angular.element(element.find("div")[0])
                .html('')
                .append("<p>" + scope.boundData.val + "</p>");
        }
    }
});

myApp.controller('FooCtrl', ['$scope', function ($scope) {
    $scope.clear = function () {
  $(".item-list").empty();
    };

    $scope.getData = function () {
        var frames = [];
        for (var i = 0; i < 2; i++) {
            var items = [];
            for (var j = 0; j < 4; j++) {
                items.push({ val : Math.floor(Math.random() * 5000) });
            };
            frames.push(items);
        }
        $scope.frames = frames;
    };

    $scope.getData();
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div ng-app="myApp">
<div ng-controller="FooCtrl">
    <div>
    <button ng-click="getData()">Refresh</button>
    <button ng-click="clear()">Clear</button>
    </div>
    <div style="float: left">
        <b>{} binding</b>
        <div ng-repeat="frame in frames track by $index">
            <div ng-repeat="item in frame track by $index">
                {{item.val}}
            </div>
        </div>
        
    </div>
    <div style="margin-left: 120px">
        <b>Using directive</b>
        <div ng-repeat="frame in frames track by $index">
            <div ng-repeat="item in frame track by $index">
                <my-directive data="item">
                    <div class="item-list"></div>
                </my-directive>
            </div>
        </div>
    </div>
    </div>
  </div>
Jim O'Neil
  • 23,344
  • 7
  • 42
  • 67

2 Answers2

0

"Thinking in AngularJS" if I have a jQuery background?

var myApp = angular.module('myApp', []);

myApp.directive("myDirective", function () {
    return {
        restrict: 'E',
        scope: {
            boundData: '=data'
        },
        link: function (scope, element) {
            angular.element(element.find("div")[0])
                .html('')
                .append("<p>" + scope.boundData.val + "</p>");
        }
    }
});

myApp.controller('FooCtrl', ['$scope', function ($scope) {
    $scope.clear = function () {
  $(".item-list").empty();
    };

    $scope.getData = function () {
        var frames = [];
        for (var i = 0; i < 2; i++) {
            var items = [];
            for (var j = 0; j < 4; j++) {
                items.push({ val : Math.floor(Math.random() * 5000) });
            };
            frames.push(items);
        }
        $scope.frames = frames;
    };

    $scope.getData();
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.js"></script>

<div ng-app="myApp">
<div ng-controller="FooCtrl">
    <div>
    <button ng-click="getData()">Refresh</button>
    <button ng-click="clear()">Clear</button>
    </div>
    <div style="float: left">
        <b>{} binding</b>
        <div ng-repeat="frame in frames track by $index">
            <div ng-repeat="item in frame track by $index">
                {{item.val}}
            </div>
        </div>
        
    </div>
    <div style="margin-left: 120px">
        <b>Using directive</b>
        <div ng-repeat="frame in frames track by $index">
            <div ng-repeat="item in frame track by $index">
                <my-directive data="item">
                    <div class="item-list"></div>
                </my-directive>
            </div>
        </div>
    </div>
    </div>
  </div>

Believe your problem is because of using a jquery selector without a specified parent being element. Typically you won't need jQuery when using Angular, good to drop it initially, search on SO for how to think in AngularJS with a jQuery background for a good write up. Will add a sample here shortly.

Community
  • 1
  • 1
shaunhusain
  • 19,630
  • 4
  • 38
  • 51
  • indeed the jquery is causing the repeated values; however, the bigger issue for me is that the "Refresh" doesn't work at all. If you put a simple alert in the link statement, note it only fires when the snippet is first run and not when $scope.frames changes by virtue of pressing the "Refresh" button. – Jim O'Neil Aug 18 '15 at 12:22
  • Seems a bit wrong to change the target after an answer is given, glad you figured it out though, you can use ng-init to alias the special properties of an ng-repeat, that may be helpful for the new issue. – shaunhusain Aug 18 '15 at 20:26
  • 1
    the target was always the same, but muddled by the use of jQuery in this specific sample - which you correctly diagnosed. Marking as answer here since indeed you helped clarify usage. – Jim O'Neil Aug 19 '15 at 21:00
  • @JimO'Neil thanks for accepting this, I just noticed in checking out the response that you also have some jQuery in your controller which is a big no-no in AngularJS. DOM manipulation of any sort should only be done inside of directives for the sake of keeping the rest of your code easy to test and guarantees that changes to the view can be seen in the markup. In the clear function it should just empty out the data in the controller and the binding/watch in the ng-repeat should automatically clean up the view for you. – shaunhusain Aug 20 '15 at 02:02
0

The flaw here was the use of track by $index in my ng-repeat (specifically the inner one).

Jim O'Neil
  • 23,344
  • 7
  • 42
  • 67