angularjscustom-directiveisolated-scope

How to bind ng-click to a custom directive and call a parent function?


We are using Angular 1.4.2 and I am trying to take a count value from a directive using ng-click, pass it to a function, then pass it up to the parent controller. After some effort it is working in a plunker, but unfortunately when I tried to move this functionality back into the main code, I'm not able to get a controller to bind to the isolated scope.

Should be simple, but I've tried injecting the current controller into the directive and trying to create a new controller, but nothing happens when I press click on the button.

Here is the code:

TEMPLATE:

<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@1.4.2" data-semver="1.4.2" src="https://code.angularjs.org/1.4.2/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body>
    <div id="app" ng-app="app">
    <div ng-controller="mainCtrl">
          <my-directive ctrl-fn="ctrlFn(count = count + 10)"></my-directive> 
        </div>
      </div>
  </body>

</html>

SCRIPT:

var app = angular.module('app', []);
app.controller('mainCtrl', function($scope){
  $scope.count = 0;
  $scope.ctrlFn = function() {
      console.log('In mainCtrl ctrlFn!');
      //$scope.count += 10; Old hardcoded value.
      console.log("count is: " + JSON.stringify($scope.count));
    //Call service here
  };  
});

app.directive('myDirective', function() {
  return {
    restrict: 'E',
    scope: {
      'ctrlFn' : '&'
    },
    template: "<div><button ng-click='ctrlFn()'>Click Here</button></div>",
    link: function(scope, element, attributes) {
      scope.ctrlFn(count);
    }
  };
});

Here is the template code I'm trying to modify in the main code base:

<div>
    <div layout="row">
        <results-loader ctrl-fn="ctrlFn(count = count + 10)"></results-loader>
        <md-button class="md-raised md-primary md-button" ng-click="ctrlFn()" flex>Click Me</md-button>
    </div>
</div>

and here is where I use an existing controller in my directive as a parent controller. It's defined in a route, rather than ng-controller and is already used for this view.

myresultsCtrl.$inject = ['$scope', ' myService'];
        /* @ngInject */
        function myresultsCtrl($scope, myService) {
            $scope.count = 0;
etc...

however, it apparently isn't bound properly as I never hit the directive or this function with ng-click.

Like I said I tried adding a new controller to the template, then I tried injecting an existing controller into the directive, but neither worked. I took out the template from the directive and tried to put the ng-click directly into the template with ctrl-fn, but I wasn't sure how to wire up the click with the call to the ctrl-fn attribute of the directive with both in the template? The idea here is to move the template into it's own html file and reference it from the directive, as in: template: "myFile.html. I'm trying to enscapsulate as much as possible to make this into a reusable component.

I haven't worked much with custom directives.

Here is the direct link to the plunker.


Solution

  • I prefer not to answer my own questions as that can have a negative appearance, but in this case, I don't know if I could have explained the question well enough to get the right answer. What was throwing me off was integrating my working plunker code into our existing code base.

    Where I got stuck was how to setup the controller properly for this use case. Though I declared my controller, in the module and injected the controller as it was already bound to that view, I needed to define the controller in the directive itself. Once I did all three together, everything started working.

    Here are some code snippets:

    angular.module('directiveName', [])
            .directive('directiveName', directiveName)
            .controller('injectedCtrl', injectedCtrl)
            etc...
    
    var directive = {
            restrict: 'E',
            scope: {
                'ctrlFn' : '&'
            },
            template: "<div><button ng-click='ctrlFn()'>Click Here</button></div>",
            controller: "injectedCtrl",
            link: function(scope, element, attributes) {
                scope.ctrlFn(); //This will pass to the parent controller: injectedCtrl, where $scope resides.
            }
        }
        return directive;   
    }
    
    
    injectedCtrl.$inject = ['$scope', 'myService'];
        /* @ngInject */
        function injectedCtrl($scope, myService) {
        $scope.ctrlFn = function() {
            //do something
        }
        etc...
    

    HTML CODE:

    When the button is clicked from the directive, ctrlFn here is a reference that is under my directives isolated scope in the DDO. This takes the external function that was passed in, namely: ctrlFn() which can then be invoked from the directive to call the parent controller.

    <div layout="row">
            <results-loader ctrlFn="ctrlFn()"></results-loader> 
        </div>
    

    I'm posting this to help someone else that might have this use case as it was not easy to figure this out.

    Dah Wahlins post goes into this subject in greater detail: Creating Custom AngularJS Directives Part 3 - Isolate Scope and Function Parameters and what helped to get my thinking straightened out.