Angular: calling controller function inside a directive link function using & - angularjs

We're running into a problem trying to call a function passed into a directive using the ampersand '&' in our directive's link function.
It seems the function is called on the controller but no arguments are passed in the call. All the examples we have seen involve passing through by creating a call in template. Is there a way to call a function on your directive from its template, then do something in the directive that calls the controller function passed into it?

Are you passing the arguments inside {}s? E.g., inside the directive's link function, you'll want to call the method like so: scope.someCtrlFn({arg1: someValue});
<div my-directive callback-fn="ctrlFn(arg1)"></div>
app.directive('myDirective', function() {
return {
scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.someCtrlFn({arg1: 22});
},
}
});
function MyCtrl($scope) {
$scope.ctrlFn = function(test) {
console.log(test);
}
}
Fiddle.

In addition to Mark's answer I'd like to point out that you can spare some letters using the & shorthand. This will assume that the callback-fn referenced in your HTML exists as scope.callbackFn in your scope. Everthing else is still the same, so there are only 2 lines to change. I kept Mark's version as comments, so you should be able to spot the difference easily.
<div my-directive callback-fn="ctrlFn(arg1)"></div>
app.directive('myDirective', function() {
return {
scope: { callbackFn: '&' }, //scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.callbackFn({arg1: 22}); //scope.someCtrlFn({arg1: 22});
},
}
});
function MyCtrl($scope) {
$scope.ctrlFn = function(test) {
console.log(test);
}
}

Related

Angularjs: How to use function in a custom directive that is declared from the controller [duplicate]

We're running into a problem trying to call a function passed into a directive using the ampersand '&' in our directive's link function.
It seems the function is called on the controller but no arguments are passed in the call. All the examples we have seen involve passing through by creating a call in template. Is there a way to call a function on your directive from its template, then do something in the directive that calls the controller function passed into it?
Are you passing the arguments inside {}s? E.g., inside the directive's link function, you'll want to call the method like so: scope.someCtrlFn({arg1: someValue});
<div my-directive callback-fn="ctrlFn(arg1)"></div>
app.directive('myDirective', function() {
return {
scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.someCtrlFn({arg1: 22});
},
}
});
function MyCtrl($scope) {
$scope.ctrlFn = function(test) {
console.log(test);
}
}
Fiddle.
In addition to Mark's answer I'd like to point out that you can spare some letters using the & shorthand. This will assume that the callback-fn referenced in your HTML exists as scope.callbackFn in your scope. Everthing else is still the same, so there are only 2 lines to change. I kept Mark's version as comments, so you should be able to spot the difference easily.
<div my-directive callback-fn="ctrlFn(arg1)"></div>
app.directive('myDirective', function() {
return {
scope: { callbackFn: '&' }, //scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.callbackFn({arg1: 22}); //scope.someCtrlFn({arg1: 22});
},
}
});
function MyCtrl($scope) {
$scope.ctrlFn = function(test) {
console.log(test);
}
}

Call method in controller from directive

HTML :
<div id="idOfDiv" ng-show="ngShowName">
Hello
</div>
I would like to call the function which is declared in my controller from my directive.
How can I do this? I don't receive an error when I call the function but nothing appears.
This is my directive and controller :
var d3DemoApp = angular.module('d3DemoApp', []);
d3DemoApp.controller('mainController', function AppCtrl ($scope,$http, dataService,userService,meanService,multipartForm) {
$scope.testFunc = function(){
$scope.ngShowName = true;
}
});
d3DemoApp.directive('directiveName', [function($scope) {
return {
restrict: 'EA',
transclude: true,
scope: {
testFunc : '&'
},
link: function(scope) {
node.on("click", click);
function click(d) {
scope.$apply(function () {
scope.testFunc();
});
}
};
}]);
You shouldn't really be using controllers and directives. Angularjs is meant to be used as more of a component(directive) based structure and controllers are more page centric. However if you are going to be doing it this way, there are two ways you can go about it.
First Accessing $parent:
If your directive is inside the controllers scope you can access it using scope.$parent.mainController.testFunc();
Second (Preferred Way):
Create a service factory and store your function in there.
d3DemoApp.factory('clickFactory', [..., function(...) {
var service = {}
service.testFunc = function(...) {
//do something
}
return service;
}]);
d3DemoApp.directive('directiveName', ['clickFactory', function(clickFactory) {
return {
restrict: 'EA',
transclude: true,
link: function(scope, elem) {
elem.on("click", click);
function click(d) {
scope.$apply(function () {
clickFactory.testFunc();
});
}
};
}]);
Just a tip, any time you are using a directive you don't need to add $scope to the top of it. scope and scope.$parent is all you really need, you will always have the scope context. Also if you declare scope :{} in your directive you isolate the scope from the rest of the scope, which is fine but if your just starting out could make things quite a bit more difficult for you.
In your link function you are using node, which doesn't exist. Instead you must use element which is the second parameter to link.
link: function(scope, element) {
element.on("click", click);
function click(d) {
scope.$apply(function() {
scope.testFunc();
});
}

Typeerror - Not a function in angular directive

I have defined a custom click directive as below:
(function() {
'use strict';
angular.module('myApp.core')
.directive('customClick', customClick);
customClick.$inject = ['$rootScope'];
function customClick() {
return {
restrict: 'A',
/*scope: {
customClick: '&'
},*/
link: function(scope, element, attrs){
$(element).on('click', function(e) {
scope.$apply(function() {
console.log("Hello..customClick..");
scope.customClick();
});
});
}
};
}
})();
And I get the following error on this;
Error logged by WDPR Angular Error handler service {xx.."stacktrace":"TypeError: a.customClick is not a function","cause":"unknown cause"}(anonymous function)bowerComponents.js:5745
How can I resolve this? If I add scope with '&' I get demanding isolated scope. Hence how to resolve it?
If I remove - scope.customClick();, it does not show anything on second html for custom-click, it has impact on only 1 html, and its controller. I want to use it in multiple controller + html.
customClick is a function on the directive itself. It is not a function on the scope. That's why the error has occurred.
link is used to manipulate dom/add event handlers on elements, which you have rightly done with element.bind('click', function() {
Whenever click occurs, the function binded to click automatically gets invoked. There is no need of watch and the invoking statement.
Your link can just be
link: function(scope, element){
element.bind('click', function() {
console.log("Hello..customClick..");
});
}
As you have used camel case in naming the directive, exercise caution in its usage in template.
You can use it as <p custom-click>hi</p>
I would recommend you to avoid using jQuery in angular apps. Try following
angular.module('newEngagementUI.core')
.directive('customClick', customClick);
customClick.$inject = ['$rootScope'];
function customClick() {
return {
restrict: 'A',
scope: {
customClick: '&'
},
link: function(scope, element, attrs){
element.bind('click', function () {
scope.customClick();
})
}
};
}
In your template:
<div custom-click="clickFunction"></div>
And your template controller should be like:
angular.module('myApp', []).controller(['$scope', function ($scope) {
$scope.clickFunction = function () {
alert('function passed');
}
}])
Working fiddle here: https://jsfiddle.net/xSaber/sbqavcnb/1/

loop function through nested directive - angularjs

I try to loop a function through a nested directive. From the console.info in myCtrl I would expect the string "this should be logged".
angular.module('myApp')
.controller('myCtrl', function ($scope) {
$scope.aFunction = function(input) {
console.info(input.message);
}
})
.directive('levelOneDirective', function () {
return {
templateUrl: '<level-two-directive aFunction="aFunction(object)"></level-two-directive>',
restrict: 'EA',
scope: {
aFunction:"&"
},
link: function (scope, element, attrs) {
}
};
})
.directive('levelTwoDirective', function () {
return {
templateUrl: '<div ng-click="aFunction({message: 'this should be logged'})"></div>',
restrict: 'EA',
scope: {
aFunction:"&"
},
link: function (scope, element, attrs) {
}
};
});
And in my index.html I have something like:
<div ng-controller="myCtrl">
<level-one-directive aFunction="aFunction(object)"></level-one-directive>
</div>
But the console says undefined.
How to connect a function through nested directives?
You have several mistakes in your code but I assume it's because you try to adjust it to the question (such as aFunction as attribute instead of a-function and templateUrl instead of template).
You can have a 2-way binding (=) in your directives (both of them):
scope: {
aFunction:"="
},
And pass the function reference without the object:
<level-one-directive a-function="aFunction"></level-one-directive>
In the second directive HTML have:
<div ng-click="invokeFunction()"></div>
And then in the link function of your 2nd directive you can do:
scope.invokeFunction = function () {
scope.aFunction({message: 'this should be logged'});
}
The above works and I find it more convenient than & binding, which as you can see, is not quite easy to work with, and frankly I haven't messed around enough with it to figure out how (and if possible) to pass arguments through it.
I've seen this question, but it's binding straight on the link function, and you want it with an ng-click so it might not work for you. But perhaps you'll find your solution there.

How to bind content of tag into into directive's scope?

Say I have a directive like such:
<my-directive>This is my entry!</my-directive>
How can I bind the content of the element into my directive's scope?
myApp.directive('myDirective', function () {
return {
scope : {
entry : "" //what goes here to bind "This is my entry" to scope.entry?
},
restrict: "E",
template: "<textarea>{{entry}}</textarea>"
link: function (scope, elm, attr) {
}
};
});
I think there's much simpler solution to the ones already given. As far as I understand, you want to bind contents of an element to scope during initialization of directive.
Given this html:
<textarea bind-content ng-model="entry">This is my entry!</textarea>
Define bind-content as follows:
directive('bindContent', function() {
return {
require: 'ngModel',
link: function ($scope, $element, $attrs, ngModelCtrl) {
ngModelCtrl.$setViewValue($element.text());
}
}
})
Here's a demo.
I may have found a solution. It relies on the transclude function of directives. It works, but I need to better understand transclusion before being sure this is the right way.
myApp.directive('myDirective', function() {
return {
scope: {
},
restrict: 'E',
replace: false,
template: '<form>' +
'<textarea ng-model="entry"></textarea>' +
'<button ng-click="submit()">Submit</button>' +
'</form>',
transclude : true,
compile : function(element,attr,transclude){
return function (scope, iElement, iAttrs) {
transclude(scope, function(originalElement){
scope.entry = originalElement.text(); // this is where you have reference to the original element.
});
scope.submit = function(){
console.log('update entry');
}
}
}
};
});
You will want to add a template config to your directive.
myApp.directive('myDirective', function () {
return {
scope : {
entry : "=" //what goes here to bind "This is my entry" to scope.entry?
},
template: "<div>{{ entry }}</div>", //**YOU DIDN'T HAVE A TEMPLATE**
restrict: "E",
link: function (scope, elm, attr) {
//You don't need to do anything here yet
}
};
});
myApp.controller('fooController', function($scope){
$scope.foo = "BLAH BLAH BLAH!";
});
And then use your directive like this:
<div ng-controller="fooController">
<!-- sets directive "entry" to "foo" from parent scope-->
<my-directive entry="foo"></my-directive>
</div>
And angular will turn that into:
<div>THIS IS MY ENTRY</div>
Assuming that you have angular setup correctly and are including this JS file onto your page.
EDIT
It sounds like you want to do something like the following:
<my-directive="foo"></my-directive>
This isn't possible with ELEMENT directives. It is, however, with attribute directives. Check the following.
myApp.directive('myDirective', function () {
return {
template: "<div>{{ entry }}</div>", //**YOU DIDN'T HAVE A TEMPLATE**
restrict: "A",
scope : {
entry : "=myDirective" //what goes here to bind "This is my entry" to scope.entry?
},
link: function (scope, elm, attr) {
//You don't need to do anything here yet
}
};
});
Then use it like this:
<div my-directive="foo"></div>
This will alias the value passed to my-directive onto a scope variable called entry. Unfortunately, there is no way to do this with an element-restricted directive. What is preventing it from happening isn't Angular, it is the html5 guidelines that make what you are wanting to do impossible. You will have to use an attribute directive instead of an element directive.

Resources