How can I use ng-click on a directive with isolate scope?
Asked Answered
I

6

6

I can get ng-click to work when the scope is inherited on a directive but not when isolated. UPDATE: The point is that I want the click function to be defined as part of the directive... moving the function definition into a different scope is not what I want.

Here's the working example with inherited scope: https://codepen.io/anon/pen/PGBQvj

Here's the broken example with isolated scope; https://codepen.io/anon/pen/jrpkjp

(Click the numbers, they increment in the first example but not in the second)

Some code...

The HTML

<div ng-app="myApp" ng-controller="baseController">
  <my-directive ng-click="hello()" current="current"></my-directive>
</div>

The directive with inherited scope:

angular.module('myApp', [])
    .controller('baseController', function($scope) {
    $scope.current = 1;
  })
    .directive('myDirective', function() {
    return {
      link: function(scope, element, attrs) {      
        scope.hello = function() {
          scope.current++
        };
      },
      replace: true,
      scope: true,
      template: '<div><child>      <strong>{{ current }}</strong></child></div>'
    }
    })
  .directive('child', function() {
    return {
      link: function(scope, element, attrs) {
        console.log("horeee");
      }
    }
  });

The same directive but with isolated scope:

angular.module('myApp', [])
    .controller('baseController', function($scope) {
    $scope.current = 1;
  })
    .directive('myDirective', function() {
    return {
      link: function(scope, element, attrs) {      
        scope.hello = function() {
          scope.current++
        };
      },
      replace: true,
      scope: {
        current:'='
      },
      template: '<div><child>      <strong>{{ current }}</strong></child></div>'
    }
    })
  .directive('child', function() {
    return {
      link: function(scope, element, attrs) {
        console.log("horeee");
      }
    }
  });
Incommensurable answered 14/10, 2016 at 16:17 Comment(0)
C
3

The problem is you're trying to call a function that is undefined. If you wish the logic to be defined inside the isolated directive, there is no need to pass in a function reference.

<my-directive current="current"></my-directive>

You cannot pass ng-click="hello()" here. This is the scope of the controller, so hello() is undefined.

Instead, move the ng-click event to the template of the directive

template: '<div ng-click="hello()">

One additional point: You're using the link() function of the directive. This is reserved for DOM manipulation. Instead, define hello() within the controller function.

controller: function ($scope) {
  $scope.hello = function() {
      $scope.current++
  }
},

I think there is a larger architectural problem here, though. The point of an isolated directive, or component, is to encapsulate logic internal to itself. It should not manipulate external state. In this example, you're incrementing a number. What if, in another part of your application, you wish to decrement a number? Copying the directive with decrement logic would be a lot of code duplication.

Instead, you should define the increment, or decrement, functionality in the controller, and pass it through to the directive.

<my-directive change-number="increment()" current="current"></my-directive>

Then, use the & syntax to bind the function reference to the directive:

scope: {
  current:'=',
  changeNumber: '&'
},

and call changeNumber from the template. Doing so very much facilitates code reuse.

Clastic answered 15/10, 2016 at 9:9 Comment(0)
P
0

Add the hello function to the controller. Let it update the value of current and pass that into the isolated scope of the directive

 .controller('baseController', function($scope) {
    $scope.current = 1;
    $scope.hello = function() {         
      $scope.current++;

    };

})

CodePen

Plant answered 14/10, 2016 at 16:42 Comment(0)
E
0

you need to pass the object from controller to directive

change your js to

angular.module('myApp', [])
.controller('baseController', function($scope) {
$scope.current = {};
$scope.current.val=1;
})
.directive('myDirective', function() {
return {
  link: function(scope, element, attrs) {      scope.internalcurrent= scope.current || {};
                                         //scope.internalcurrent.val=1;
    scope.internalcurrent.hello = function() {
      scope.internalcurrent.val++

    };
  },
  replace: true,
  scope: {
    current:'='
  },
  template: '<div><child>      <strong>{{ internalcurrent.val }}</strong></child></div>'
}
})
.directive('child', function() {
return {
  link: function(scope, element, attrs) {
    console.log("horeee");
  }
}
});

and your html to

<div ng-app="myApp" ng-controller="baseController">
<my-directive ng-click="hello()" current="current"></my-directive>
</div>
Erdei answered 14/10, 2016 at 18:16 Comment(0)
P
0

If you want the click function to be defined within the directive then you need to attach the handler within the template like this:

template: '<div ng-click="hello()"><child><strong>{{current}}</strong></child></div>'

When you attach the handler to the directive declaration as in your broken example you are basically telling ng-click that you want to call a function on the current scope (the scope of the controller). This may not seem like what was happening because in your working example your function was defined from within myDirective, but it was actually being attached to the scope, which in this case (the working example) was the scope of the controller.

CodePen

Plant answered 15/10, 2016 at 7:26 Comment(0)
F
0

Another solution, if you cannot move the "ng-click" into the directive template, is that you can place the function in the parent scope. Like this:

Replace:

link: function(scope, element, attrs) {      
    scope.hello = function() {
      scope.current++
    };
  }

For:

link: function(scope, element, attrs) {      
    scope.$parent.hello = function() {
      scope.current++
    };
  }

This will make the function "hello" available from the controller's scope.

Finny answered 19/7, 2023 at 21:43 Comment(0)
I
-1

With isolated directive, you can use ng-click="$parent.hello()" instead of ng-click="hello()". Nice to post pens in your question.

Explanation: When you use ng-click on an element, the value of it (hello()) will be evaluated relative to the outer scope of it, ie the scope of controller in your example. When you create a directive that uses non-isolated scope, the scope of controller is passed to the link function, and hello is defined on the scope of controller.

// Edit code:

angular.module('myApp', [])
    .controller('baseController', function($scope) {
    $scope.current = 1;
    $scope.hello = () => $scope.current ++;
  })
    .directive('myDirective', function() {
    return {
      link: function(scope, element, attrs) {      
      },
      replace: true,
      scope: {
        current:'='
      },
      template: '<div><child>      <strong>{{ current }}</strong></child></div>'
    }
    })
  .directive('child', function() {
    return {
      link: function(scope, element, attrs) {
        console.log("horeee");
      }
    }
  });
Intelligence answered 14/10, 2016 at 16:20 Comment(6)
Sorry, I misunderstand your question. Define hello() on the scope of you controller and it should work.@IncommensurableIntelligence
That doesn't work either. Please try it before suggesting :) tx.Incommensurable
Unless I'm misunderstanding you hello() is already defined on the scope of the controller.Incommensurable
When you use a directive that has isolated scope, it only effects evaluation inside the element. @IncommensurableIntelligence
Your solution works but it's not actually useful to me. I don't want to have to move my function out of the directive controller.Incommensurable
Move ng-click to your directive template. <div ng-click="hello()"><child><strong>{{ current }}</strong></child></div> @IncommensurableIntelligence

© 2022 - 2024 — McMap. All rights reserved.