3

I have a directive that has a local scope where a partial contains ng-click.

The Fiddle is there: http://jsfiddle.net/stephanedeluca/QRZFs/13/

Unfortunatelly, since I moved my code to the directive, ng-click does not fire anymore.

The controller and the directive is as follows:

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

app.directive('plantStages', function ($compile) {
    return {
        restrict: 'E',
        transclude: true,
        template: '<figure class="cornStages">\
                        <p ng-transclude style="color: skyblue"></p>\
                        <hr/>\
                        <p ng-bind-html="title"></p>\
                        <p ng-bind-html="subtitle">{{subtitle}}</p>\
                        <ul>\
                            <li ng-repeat="stage in stages" ng-click="changePage(stage)">{{stage}}</li>\
                        </ul>\
                    </figure>',
        scope: {
            stages:"=",
            title:'@'
        },
        link: function (scope, element, attrs, ctrl, transclude) {
            if (!attrs.title) scope.title = "Default title";
        }
    };
});

app.controller('myCtrl', function ($scope, $location, $http) {
    $scope.stages = ['floraison', 'montaison'];
    $scope.changePage = function (page) {
        var url = "corn.page.html#/"+page;
        console.log("Change page "+page+" with url "+url);
        alert("about to change page as follows: document.location.href = "+url);
    };

});

The html that invokes it is as follows:

<div ng-controller="myCtrl">
    Stages, 
    <p ng-repeat="stage in stages">{{stage}}</p>
    <hr/>
    Plant stages
    <plant-stages 
        title="<b>Exploration<br/>du cycle</b>"
        subtitle="<em>This is a<br/>sub title</em>"
        stages="stages"
    >
        Inner<br/>directive
    </plant-stages>
</div>

Any idea?

Stéphane de Luca
  • 12,745
  • 9
  • 57
  • 95
  • 1
    Salut,check this answer http://stackoverflow.com/questions/16488769/ng-click-doesnt-work-within-the-template-of-a-directive – mpm May 03 '14 at 11:22

2 Answers2

7

You can't access changePage() defined in controller's scope from directive directly, since your directive has isolated scope. However, there are still several ways to do it:

Option 1:

Option 1 is the most simple option. However it is much like a workaround and I don't recommend to use it widely. You can get your controller's scope from element passed to link function and invoke changePage there:

link: function (scope, element, attrs, ctrl, transclude) {
    if (!attrs.title) scope.title = "Default title";
    scope.changePage = element.scope().changePage; // <= Get parent scope from element, it will have changePage()
}

Option 2:

If you don't have any logic that involves scope defined in the outer controller (as in your example), you can define inner controller for your directive and perform it there:

app.directive('plantStages', function ($compile) {
    return {
       ...
       controller: ['$scope', function($scope) {
           $scope.changePage = function(page) {
               var url = "corn.page.html#/"+page;
               console.log("Change page "+page+" with url "+url);
               alert("about to change page as follows: document.location.href = "+url);
           }
       }]
    };
});

Option 3:

If you want do reuse logic defined in changePage() in different directives and controllers, the best way to do it is to move the logic to some service that may be injected to both controller and directive:

app.service('changePageService', function() {
    this.changePage = function(page) {
        var url = "corn.page.html#/"+page;
        console.log("Change page "+page+" with url "+url);
        alert("about to change page as follows: document.location.href = "+url);
    }
});

app.controller('myCtrl', function ($scope, $location, $http, changePageService) {
    ...
    changePageService.changePage('page');
    ...
});

app.directive('plantStages', function ($compile) {
    ...
    controller: ['$scope', 'changePageService', function($scope, changePageService) {
        $scope.changePage = changePageService.changePage;
    }]
    ...
});

Option 4:

You can pass piece of code like changePage(page) as value of some attribute of the directive and inside directive define scope property with '&' that will create a function that will be executed in the outer controller's scope with arguments passed to that function. Example:

JavaScript

app.directive('plantStages', function ($compile) {
    return {
        restrict: 'E',
        transclude: true,
        template: '<figure class="cornStages">\
                        <p ng-transclude style="color: skyblue"></p>\
                        <hr/>\
                        <p ng-bind-html="title"></p>\
                        <p ng-bind-html="subtitle"></p>\
                        <ul>\
                            <li ng-repeat="stage in stages" ng-click="changePage({page: stage})">{{stage}}</li>\
                        </ul>\
                    </figure>',
        scope: {
            stages:"=",
            title:'@',
            changePage:'&'
        },
        link: function (scope, element, attrs, ctrl, transclude) {
            if (!attrs.title) scope.title = "Default title";
        }
    };
});

HTML

<div ng-controller="myCtrl">
    Stages, 
    <p ng-repeat="stage in stages">{{stage}}</p>
    <hr/>
    Plant stages
    <plant-stages 
        title="<b>Exploration<br/>du cycle</b>"
        subtitle="<em>This is a<br/>sub title</em>"
        stages="stages"
        change-page="changePage(page)"
    >
        Inner<br/>directive
    </plant-stages>

Plunker: http://plnkr.co/edit/s4CFI3wxs0SOmZVhUkC4?p=preview

Vadim
  • 8,701
  • 4
  • 43
  • 50
  • Wow, very useful and extensive answer, you make my day. One point though, (I gave a test to all 3 options, and kept #2) The page switching is fire at document load. How to prevent this? (my fiddle http://jsfiddle.net/stephanedeluca/QRZFs/27/ ) – Stéphane de Luca May 03 '14 at 15:22
  • 1
    @StéphanedeLuca In your fiddle you pasted also `changePageService.changePage('page');` in to `myCtrl` controller that I added as example of how `changePageService` service may be reused and consumed inside controller. You should remove or comment this line and there will be no page switching at document load. http://jsfiddle.net/L8VbK/2/ – Vadim May 04 '14 at 06:16
  • Oops, you're right (it explains why I hadn't the issue in my app!) Thanks a lot Vadim. – Stéphane de Luca May 04 '14 at 14:34
1

The idea of directives is to treat them as reusable components and avoid external dependencies wherever possible. If you have the possibility to define the behavior of your directive in its own controller then do it.

module.directive('myDirective', function () {
  return {
    restrict: 'E',
    controller: function() { /* behaviour here */ },
    template: '<div>Directive Template</div>',
        scope: {
            /* directive scope */
        }
    };
});

If this is not possible you can pass the function as explained in the linked question (see comment above). Check the updated fiddle.

Sebastian
  • 16,813
  • 4
  • 49
  • 56
  • That's precisely what makes me uncomfortable if I must pass the function in (even the name). That's an architecture weakness to me :-/ – Stéphane de Luca May 03 '14 at 15:06
  • 1
    You are right. That makes the directive harder to reuse. Better encapsulate the behavior (in your case `changePage()`) in the controller of the directive http://jsfiddle.net/N6P6T/2/ – Sebastian May 03 '14 at 15:21