rlcrews
rlcrews

Reputation: 3562

Calling a function of parent controller with nested directives using isolated scope

I have a a treeview directive that I am trying to invoke a function from the parent controller and I can't seem to get the function to get called. I'm not sure if it's due to the structure of the treeview and nesting of child elements or what.

Within my html I have the directive declared as:

<div ng-controller="treeController as vm">
    <tree src="myList" filter="doSomething()"></tree>
    <a ng-click="clicked()"> link</a>
</div>

I declared in the directive an attribute/parameter filter which should call the doSomething() function within the main controller.

The main controller contains the following code (test to build the tree as well as invoke the function.

app.controller("treeController", ['$scope', function($scope) {

    var vm = this;

   $scope.doSomething = function () {
        var item = data;
   }

   $scope.clicked = function () {
       alert('clicked');
   }

        $scope.myList = {
            children: [
              {
                  name: "Event",
                  children: [
                    {
                        name: "Event Date",
                        children: [
                          {
                              name: "2008",
                              FilterType: '_eventStartDate',
                              Parent: '_event'
                          },
                          {
                              name: "2009",
                              FilterType: '_eventStartDate',
                              Parent: '_event'
                          }
                        ]
                    },
                    {
                        name: "Event Attendee",
                        children: [
                          {
                              name: "Person 1",
                              FilterType: '_eventAttenddeeName',
                              Parent: '_Event'
                          },
                          {
                              name: "Person 2",
                              FilterType: '_eventAttenddeeName',
                              Parent: '_Event'
                          }
                        ]
                    }
                  ]
              }]
        };
}]);

The within my directive I declare the isolated scope, as well as the parameter filter (second app.directive) which I prefix with the model binding prefix '&'. Within the template I then call ng-click which should invoke the function doSomething() within the main controller. However... no dice.

app.directive('tree', function() {
//builds the tree
    return {
        restrict: 'E', 
        replace: true,
        scope: {
            t: '=src'
        },
        template: '<ul><branch ng-repeat="c in t.children" src="c"></branch></ul>'
    };
});

app.directive('branch', function($compile) {
//directive that builds the children/branches
    return {
        restrict: 'E', 
        replace: true, 
        scope: {
            b: '=src',
            filter: '&'
        },
        template: '<li><input type="checkbox" ng-click="filter()" ng-hide="visible" /><a>{{ b.name }}</a></li>',
        link: function (scope, element, attrs) {

           var has_children = angular.isArray(scope.b.children);
            scope.visible = has_children;
            if (has_children) {
                element.append('<tree src="b"></tree>');

                $compile(element.contents())(scope);
            }

            element.on('click', function(event) {
                event.stopPropagation();

                if (has_children) {
                    element.toggleClass('collapsed');
                }
            });
            //test to call function within directive
            //scope.doSomething = function(b) {
            //    alert('test');
            //}
        }
    };
});

I posted a public jsFiddle with the working code sample as well

Any suggestions on what I missed?

Right now I am just trying to invoke the method however ultimately I will need to pass as a parameter the selected item back to the controller as well but for now I'm just trying to figure out why the function in my controller will not get called.

Thanks in advance

Update: It was suggested to move the declaration of the filter from the branch to the tree directive.

I updated my code locally so the tree directive looked like the following:

app.directive('tree', function() {
    return {
        restrict: 'E', 
        replace: true,
        scope: {
            t: '=src',
            filter: '&'
        },
        template: '<ul><branch ng-repeat="c in t.children" src="c"></branch></ul>'
    };
});

Note: the filter parameter was removed from the secondary directive. There was no change in the output. The function within the controller was still not called.

Upvotes: 0

Views: 510

Answers (2)

rlcrews
rlcrews

Reputation: 3562

Update: Sundar's comments got me down the right path here is the updated directive the main issue for me was that I am working with nested directives so the nested item (which was making the function call) was out of scope of the controller to correct this included Sundar's changes but to get the nested directive to work I had to explicitly set the controller at the parent directive level. I realize this is not a good option if you needed a directive to be used in multiple areas of an app. However for me the directive is only used in one spot so this solution works. If anyone has any other suggestions or better approaches I'd appreciate them.

app.directive('tree', function() {
    return {
        restrict: 'E', 
        replace: true,
        scope: {
            t: '=src',
            filter: '&'
        },
        controller:'treeController', //explicitly set the controller of the parent directive
        template: '<ul><branch ng-repeat="c in t.children" src="c" filter="doSomething(data)"></branch></ul>'
    };
});

app.directive('branch', function($compile) {
    return {
        restrict: 'E', 
        replace: true, 
        scope: {
            b: '=src',
            filter: '&'
        },

        template: '<li><input type="checkbox" ng-click="innerCall()" ng-hide="visible" /><a>{{ b.name }}</a></li>',
        link: function (scope, element, attrs) {

           var has_children = angular.isArray(scope.b.children);
            scope.visible = has_children;
            if (has_children) {
                element.append('<tree src="b"></tree>');

                $compile(element.contents())(scope);
            }

            element.on('click', function(event) {
                event.stopPropagation();

                if (has_children) {
                    element.toggleClass('collapsed');
                }
            });
            scope.innerCall = function() {
                scope.filter(scope.b);
            }
        }
    };
});

Upvotes: 0

sundar
sundar

Reputation: 408

your tree directive does not have filter method.your branch directive only has that property

<div ng-controller="treeController as vm">
    <tree src="myList" filter="doSomething()"></tree>
    <a ng-click="clicked()"> link</a>
</div>

app.directive('tree', function() {
//builds the tree
    return {
        restrict: 'E', 
        replace: true,
        scope: {
            t: '=src',
            filter: '&'
        },
        template: '<ul><branch ng-repeat="c in t.children" src="c" filter="doSomething()"></branch></ul>'
    };
});

app.directive('branch', function($compile) {
//directive that builds the children/branches
    return {
        restrict: 'E', 
        replace: true, 
        scope: {
            b: '=src',
            filter: '&'
        },
        template: '<li><input type="checkbox" ng-click="filter()" ng-hide="visible" /><a>{{ b.name }}</a></li>',
        link: function (scope, element, attrs) {

           var has_children = angular.isArray(scope.b.children);
            scope.visible = has_children;
            if (has_children) {
                element.append('<tree src="b"></tree>');

                $compile(element.contents())(scope);
            }

            element.on('click', function(event) {
                event.stopPropagation();

                if (has_children) {
                    element.toggleClass('collapsed');
                }
            });
            //test to call function within directive
            //scope.doSomething = function(b) {
            //    alert('test');
            //}
        }
    };
});

Upvotes: 0

Related Questions