How to properly bind scope between directive and controller with angularJS

user2165994 picture user2165994 · Mar 13, 2013 · Viewed 40k times · Source

I'm trying to generate an n-level hierarchical unordered list with anugularJS, and have been able to successfully do so. But now, I'm having scope issues between the directive and controller. I need to change a scope property of the parent from within a function called via ng-click in the directive template.

See http://jsfiddle.net/ahonaker/ADukg/2046/ - here's the JS

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

//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});

function MyCtrl($scope) {
    $scope.itemselected = "None";
    $scope.organizations = {
        "_id": "SEC Power Generation",
        "Entity": "OPUNITS",
        "EntityIDAttribute": "OPUNIT_SEQ_ID",
        "EntityID": 2,
        "descendants": ["Eastern Conf Business Unit", "Western Conf Business Unit", "Atlanta", "Sewanee"],
        children: [{
            "_id": "Eastern Conf Business Unit",
            "Entity": "",
            "EntityIDAttribute": "",
            "EntityID": null,
            "parent": "SEC Power Generation",
            "descendants": ["Lexington", "Columbia", "Knoxville", "Nashville"],
            children: [{
                "_id": "Lexington",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 10,
                "parent": "Eastern Conf Business Unit"
            }, {
                "_id": "Columbia",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 12,
                "parent": "Eastern Conf Business Unit"
            }, {
                "_id": "Knoxville",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 14,
                "parent": "Eastern Conf Business Unit"
            }, {
                "_id": "Nashville",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 4,
                "parent": "Eastern Conf Business Unit"
            }]
        }]
    };

    $scope.itemSelect = function (ID) {
        $scope.itemselected = ID;
    }
}

app.directive('navtree', function () {
    return {
        template: '<ul><navtree-node ng-repeat="item in items" item="item" itemselected="itemselected"></navtree-node></ul>',
        restrict: 'E',
        replace: true,
        scope: {
            items: '='
        }
    };
});

app.directive('navtreeNode', function ($compile) {
    return {
        restrict: 'E',
        template: '<li><a ng-click="itemSelect(item._id)">{{item._id}} - {{itemselected}}</a></li>',
        scope: {
            item: "=",
            itemselected: '='
        },
        controller: 'MyCtrl',
        link: function (scope, elm, attrs) {
            if ((angular.isDefined(scope.item.children)) && (scope.item.children.length > 0)) {
                var children = $compile('<navtree items="item.children"></navtree>')(scope);
                elm.append(children);
            }
        }
    };
});

and here's the HTML

<div ng-controller="MyCtrl">
    Selected: {{itemselected}}

    <navtree items="organizations.children"></navtree>
</div>

Note the list is generated from the model. And ng-click calls the function to set the parent scope property (itemselected), but the change only occurs locally. Expected behavior, when I click on an item, is that "Selected: None" should change to "Selected: xxx" where xxx is the item that was clicked.

Am I not binding the property between the parent scope and the directive appropriately? How do I pass the property change to the parent scope?

Hope this is clear.

Thanks in advance for any help.

Answer

Rajkamal Subramanian picture Rajkamal Subramanian · Mar 13, 2013

Please have a look at this working fiddle, http://jsfiddle.net/eeuSv/

What i did was to require the parent controller inside the navtree-node directive, and call a member function defined in that controller. The member function is setSelected. Please note that its this.setSelected and not $scope.setSelected. Then define a navtree-node scope method itemSelect. While you click on the anchor tags, it will call the itemSelect method on the navtree-node scope. This inturn will call the controllers member method setSelected passing the selected id.

scope.itemSelect = function(id){ myGreatParentControler.setSelected(id) }