Reputation: 54005
How can I expose a method from a directive? I know that I should use attributes for data, but I really want to expose behavior, not data. Something that the parent controller can call.
Let's say my DOM looks like:
<div ng-app="main">
<div ng-controller="MyCtrl">
<button ng-click="call()" >Call</button>
<div id="container" my-directive> </div>
</div>
</div>
JavaScript:
angular.module("main", []).controller("MyCtrl", function($scope) {
$scope.call = function() {
$scope.myfn();
};
}).directive("myDirective", function() {
return {
// scope: {},
controller: function($scope) {
$scope.myfn = function() {
console.log("myfn called");
}
}
};
});
jsFiddle: http://jsfiddle.net/5gDjQ/7/
If the scope
is commented out (i.e. the directive does not have isolated scope), it works just fine. When I press the button, myfn
is called and logs to console.
As soon as I uncomment scope
, it doesn't work. myfn
is defined on child scope and not easily available to the parent.
In my case I think that polluting the parent scope is a bad idea and I would really like to avoid it.
So, how can I expose a function from directive to the parent controller? Or: How can I invoke a method on directive from parent controller?
Upvotes: 35
Views: 15226
Reputation: 48968
The release of AngularJS V1.7.1* introduces the new ng-ref directive.
The ng-ref attribute tells AngularJS to publish the controller of a component on the current scope. This is useful for having a component such as an audio player expose its API to sibling components. Its play and stop controls can be easily accessed.
For more information, see
Upvotes: 2
Reputation: 94
To contribuite, @georgeawg gave me a cool solution using Service to do the job. This way you can handle multiple directives on same page.
<html ng-app="myApp">
<head>
<script src="https://opensource.keycdn.com/angularjs/1.6.5/angular.min.js"></script>
</head>
<body ng-controller="mainCtrl">
<h1>^v1.6.0 ($postLink hook required)</h1>
<my-directive name="sample1" number="number1"></my-directive>
<my-directive name="sample2" number="number2"></my-directive>
</body>
<script>
angular.module('myApp', [])
.controller('mainCtrl', ['$scope', 'myDirectiveFactory', function ($scope, myDirectiveFactory) {
$scope.number1 = 10
$scope.number2 = 0
this.$postLink = function () {
myDirectiveFactory.get('sample2')
.increment()
.increment()
.increment()
.increment()
myDirectiveFactory.get('sample1')
.increment()
.increment()
myDirectiveFactory.get('sample2')
.decrement()
}
}])
.factory('myDirectiveFactory', function () {
var instance = {}
return {
get: function (name) {
return instance[name]
},
register: function (name, value) {
return instance[name] = value
},
destroy: function (name) {
delete instance[name]
}
}
})
.controller('myDirectiveCtrl', ['$scope', 'myDirectiveFactory', function ($scope, myDirectiveFactory) {
$scope.name = $scope.name || 'myDirective'
$scope.$on('$destroy', function () {
myDirectiveFactory.destroy($scope.name)
})
var service = {
increment: function () {
$scope.number++
return this
},
decrement: function () {
$scope.number--
return this
}
}
myDirectiveFactory.register($scope.name, service)
}])
.directive('myDirective', [function () {
return {
controller: 'myDirectiveCtrl',
restrict: 'E',
scope: {
number: '<',
name: '@?'
},
template: '<p> {{ number }} </p>'
}
}])
</script>
</html>
Upvotes: 1
Reputation: 57774
Rather than trying to figure out how to call a function hidden inside a directive, I think you should be asking yourself: why do I want to call a function defined in a directive?
One reason I can think of is: to trigger some behaviour of the directive that could also be triggered by the user of the application from within the directive.
If so, the obvious and Angulary thing to do would be to broadcast an event on a scope that contains the directive that should react to it. Then the directive would listen to that event and trigger its function by itself.
This has additional benefits:
Let's try to come up with a very simple example: suppose we have a widget that displays a random inspirational quote downloaded from somewhere. It also has a button to change the quote to a different one.
Here's the directive's template:
<p>{{ quote }}</p>
<button ng-click="refreshQuote()"></button>
And here's the directive's code:
app.directive("randomQuote", function () {
return {
restrict: "E",
scope: {},
link: function (scope) {
scope.refreshQuote = function () {
scope.quote = ... // some complicated code here
};
scope.refreshQuote();
}
};
});
Note that the directive is entirely self-contained: it has an isolate scope and does the quote-fetching by itself.
Let's suppose we also want to be able to refresh the quote from the controller. This could be as simple as calling this in the controller code:
$scope.$broadcast("refresh-random-quote");
To add the event handler, we must add this code to the link
function of the directive:
scope.$on("refresh-random-quote", function () {
scope.refreshQuote();
});
This way, we've created a one-way communication channel from the controller to the directive that doesn't break the isolation of the directive, and also works if the directive is nested deep in the scope hierarchy of the code that broadcasts the event.
Upvotes: 17
Reputation: 364677
How can I expose a function from directive to the parent controller?
Or: How can I invoke a method on directive from parent controller?
Well, I don't think you should be trying to do this (i.e., coupling controller behavior to a directive), but if you must... here's one way you can do it: pass a controller function to your directive, which the directive can call to notify the controller of the directive function:
<div id="container" my-directive cb="setDirectiveFn(fn)"></div>
directive("myDirective", function() {
return {
scope: { cb: '&' },
controller: function($scope) {
$scope.myfn = function() {
console.log("myfn called");
}
$scope.cb({fn: $scope.myfn});
}
};
});
Upvotes: 7
Reputation: 22446
You can do this with an isolated scope by setting up a variable in the scope that's two-way bound to the controller (using '='). In your directive you can then assign the function to that variable, and angular will use the binding to find the corresponding variable in your controller. That variable will point to a function that your controller can call.
html: Note the new attrib:
<div ng-app="main">
<div ng-controller="MyCtrl">
<button ng-click="call()" >Call</button>
<div id="container" my-directive my-fn="fnInCtrl"> </div>
</div>
</div>
js:
angular.module("main", []).controller("MyCtrl", function($scope) {
$scope.call = function() {
$scope.fnInCtrl();
};
}).directive("myDirective", function() {
return {
scope: {
myFn: '='
},
controller: function($scope) {
$scope.myFn = function() {
console.log("myfn called");
}
}
};
});
Upvotes: 27