I am using AngularJS and am trying to pass a function on my controller's scope to a directive's isolate scope.
I know I have done this before, but am having issues this time.
The function takes two inputs, and logs them to the console.
However, when I pass it to my directive as
<div my-dir my-function="functionName"></div>
or
<div my-dir my-function="functionName()"></div>
And attempt to call it from my directives controller, I actually get a function that takes no arguments and returns my function.
that is, instead of calling
ctrl.functionName(a,b)
I have to call
ctrl.functionName()(a,b)
Any help would be greatly appreciated.
I have seen some reference to syntax like this:
<div my-dir my-function="functionName({})"></div>
html:
<div my-dir my-function="functionName()"></div>
directive:
angular.directive('myDir', function() {
return {
scope: {
callback: '&myFunction'
},
controller: function($scope) {
$scope.callback({param1: val1, param2: val2});
};
});
Try the following code with scope isolation:
angular.module("Demo", [])
.controller("ChildCtrl", function($rootScope, $scope) {
$scope.testFunc = function (a, b) {
return a + b;
}
})
.directive("isoElement", function() {
return {
restrict: "E",
scope: {
testFunc: '&'
},
link: function(scope) {
console.log(scope.testFunc()(1, 2));
}
};
});
Directive usage will be:
<div ng-app="Demo" ng-controller="ChildCtrl">
<iso-element test-func="testFunc"></iso-element>
</div>
You will need to do scope.testFunc()(..) because scope.testFunc() will return the function itself. IMO this is more understandable than approaching the problem from another side.
Related
In my angular application, I defined a directive foo-directive, which is placed in a parent controller as below:
<div ng-app="app">
<div ng-controller="ParentCtrl as parent">
<foo-directive data="parent.city" tab-click="parent.tClick()" tab-click2="parent.tClick2(v)"></foo-directive>
</div>
</div>
pass two method: parent.tClick() and parent.tClick2(v) into the directive and bind to tab-click and tab-click2 attributes respectively. The difference is that the second one has a parameter
and the JS code goes as below:
function ParentCtrl($timeout){
this.city= "London";
this.tClick = function(){
console.log("debugging parent tclick...");
}
this.tClick2 = function(v){
console.log("debugging parent tclick2...");
console.log(v)
}
}
function FirstCtrl() {
this.$onInit = function(){
this.click = function(){
this.tabClick();
this.tabClick2("abc");
}
}
}
function fooDirective() {
return {
scope: {
data: '=',
tabClick : "&",
tabClick2: "&"
},
controller: 'FirstCtrl',
controllerAs: 'foo',
bindToController: true,
template: '<div ng-click="foo.click()">{{ foo.data }}</div>',
link: function ($scope, $element, $attrs, $ctrl) {
//console.log($ctrl.name);
}
};
}
Now the issue comes from the second method this.tabClick2("abc"). There is TypeError message. I have reproduced this issue with this live demo:
https://jsfiddle.net/baoqger/sv4d03hk/1/
any help?
When passing the functions into your directive, you should pass the "reference" to the function, rather than the "result" of the function. Adding the parenthesis, is actually executing the function and returning the result to the directive. As neither function returns a value, they will both be passing undefined.
Given that the value of the parameter you want to pass (v) to the function is inside the directive scope, not that of the parent, you dont need to even tell the directive that the function accepts a parameter. You just pass it to the function inside the directive. ie.
<foo-directive data="parent.city" tab-click="parent.tClick" tab-click2="parent.tClick2"></foo-directive>
According to the docs, using & is then you want to evaluate the result of the attribute:
The & binding allows a directive to trigger evaluation of an expression in the context of the original scope, at a specific time.
Instead, we want to be able to execute the passed attribute, and in particular give it a variable. As such either = (two-way binding) or # (one-way binding) are probably more appropriate for what we're after.
tabClick: "=",
tabClick2: "="
You can also do away with your controller completely by updating your template.
template: '<div ng-click="foo.tabClick();foo.tabClick2(data)">{{ foo.data }}</div>'
Updated JSFiddle
function ParentCtrl($timeout) {
this.city = "London";
this.tClick = function() {
console.log("debugging parent tclick...");
}
}
function FirstCtrl() {}
function fooDirective() {
return {
scope: {
data: '=',
tabClick: "="
},
controller: 'FirstCtrl',
controllerAs: 'foo',
bindToController: true,
template: '<div ng-click="foo.tabClick(data)">{{ foo.data }}</div>',
link: function($scope, $element, $attrs, $ctrl) {
//console.log($ctrl.name);
}
};
}
angular
.module('app', [])
.directive('fooDirective', fooDirective)
.controller('FirstCtrl', FirstCtrl)
.controller('ParentCtrl', ParentCtrl)
<script src="https://code.angularjs.org/1.6.2/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ParentCtrl as parent">
<foo-directive data="parent.city" tab-click=":: parent.tClick"></foo-directive>
</div>
</div>
PS. if you're concerned about performance using # or =, consider one time bindings using ::. ie. <foo-directive data="parent.city" tab-click=":: parent.tClick" tab-click2=":: parent.tClick2"></foo-directive>
Try the following snippet of code for your "FirstCtrl":
function FirstCtrl() {
this.$onInit = function(){
this.click = function(){
this.tabClick({v: this.data});
}
}
}
As you are using an expression binding (&), you need to explicitly call it with a JSON containing "v" and it's value. like the following:
this.tabClick({v: this.data});
I am currently writing an angular directive that uses a template in a different HTML file and an isolated template. The directive gets some string via # to its scope and that value is available in teh controller function.
Somehow its not available via {{}} in the HTML template. Why is that so? How can I change that? I read something about the template using the parent scope but I don't fully understand that.
Here is a code example:
angular.module('moduleName')
.directive('aGreatDirective', function () {
return {
restrict: 'E',
scope: {
mapid: '#'
},
templateUrl: "path/to/template.html",
controller: ['$scope', function (scope) {
console.log($scope.mapid); // is defined
}
}
});
And the html code for the template:
<div id="{{mapid}}"></div>
The result in the browser is exactly the same where it should be:
<div id="theValueOfmapid"></div>
Thanks for your help!
PS Here is a jsfiddle: fiddle
Your fiddle was incorrect since you didn't have your controller defined or $scope injected properly. The following will work just fine:
template:
<div ng-controller="MyCtrl">
<a-great-directive mapid="thisisthemapid"></a-great-directive>
Some other code
</div>
js:
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function () {
});
myApp.directive('aGreatDirective', function() {
return {
restrict: 'E',
scope: {
mapid: '#'
},
template: "<div id='{{mapid}}'> {{mapid}} </div>",
controller: ['$scope', function($scope) {
console.log($scope.mapid); // is defined
}
]}
});
Fiddle
Note that in my example, the injected variable in your directive's controller should be $scope, not scope, for consistency reasons.
I'm having troubles updating a variable on my controller's $scope from within a directive binding to those variables. For instance, I'm trying update the value with a setter-function whenever I move my mouse, but the value never gets updated.
I have made a fiddle here: http://jsfiddle.net/23hdsueb/
Any ideas how to set variables on the parent scope from my directive?
HTML:
<body ng-app="testApp" ng-controller="TestController">
<p>
outside directive
</p>
<input type="text" name="name" ng-model="obj.value">
<p>
value: {{obj.value}}
</p>
<test value="obj.value" set-value="setValue"></test>
</body>
JS:
angular.module('testApp', [])
.controller('TestController', ['$scope', function ($scope) {
$scope.obj = {
value: 'initial value'
};
$scope.setValue = function (val) {
$scope.obj.value = val;
};
}])
.directive('test', ['$document', function ($document) {
return {
restrict: 'E',
scope: {
value: '=',
setValue: '='
},
template: '<div class="test">inside directive<br>value: {{value}}</div>',
link: function (scope, element, attrs) {
$document.on('mousemove', function (event) {
scope.setValue(event.pageX + ' : ' + event.pageY);
});
}
};
}]);
Sorry for not giving the right answer in the first try, I had not understood the question properly. The answer is that your whole code is right except that you're missing one line of code. Normally you don't need to add it, but you do need to add it here.
$document.on('mousemove', function (event) {
scope.setValue(event.pageX + ' : ' + event.pageY);
scope.$apply();
});
While $document is an angularized variable, $document.on is not really the angular way of attaching event handlers to events.
Why is it good to follow angularized ways? Because they automatically run scope.$apply() when the command completes. The apply() function triggers a digest cycle which is responsible for angular's powers. The digest cycle among many things, checks if there are binded variables which need to be updated or not.
If you use ng-mousemove then you won't need the scope.$apply() line because the ng-mousemove directive triggers the digest cycle when it is fired.
What you are missing is, syntax for calling function of controller via directive /or/ How to call method in attribute of directive:
TL:DR you may go through beautiful explanation about same by Dan Wahlin
In your directive:
.directive('test', ['$document', function ($document) {
return {
restrict: 'E',
scope: {
value: '=',
setValue: '&'//for function it should be &
},
template: '<div class="test">inside directive<br>value: {{value}}</div>',
link: function (scope, element, attrs) {
$document.on('mousemove', function (event) {
//argVal should be exact name in the function call mentioned in directive's attribute
scope.setValue({argVal: event.pageX + ' : ' + event.pageY});
});
}
};
}]);
And your directive use should be like: Please Note: argVal should match exactly as in call to function from within link function call
<test value="obj.value" set-value="setValue(argVal)"></test>
For detail please refer: SO question
I have two directives and a controller, the problem is that i can't call a function 'addMarkers()' of the controller from my directive .
i have the following codes:
derectives.js
app
.directive('collection', function () {
var tpl = '<ul><member ng-repeat="member in collection" member="member"></member></ul>';
return {
restrict: "E",
replace: true,
scope: {
collection: '='
},
template: tpl
}
})
app
.directive('member', function ($compile) {
var tpl = '<li><a ng-click="addMarkers(member)" >{{member.title}}</a>'+
'<input class="align" ng-if="!member.children" type="checkbox" ng-checked="true"/></li>';
return {
restrict: "E",
replace: true,
scope: {
member: '='
},
template: tpl,
link: function (scope, element, attrs) {
if (angular.isArray(scope.member.children)) {
element.append("<collection collection='member.children'></collection>");
$compile(element.contents())(scope)
}
}
}
})
controller.js
app
.controller('IndexCtrl', function($scope, itemProvider){
itemProvider.getItems().success(function(data){
$scope.items = data;
});
$scope.addMarkers = function(item){
alert("Helloo");
$scope.markers = itemProvider.addMarkers();
}
});
index.html
<div id="menu" ng-controller="IndexCtrl">
<nav>
<h2><i class="fa fa-reorder"></i>All Categories</h2>
<collection collection='items'></collection>
</nav>
</div>
$rootScope is the global scope which should be used only when required. It should be kept as clean as possible to avoid pollution of scope variables.
In order to access parent method from isolated scope you can use $parent service, as shown below:
scope.$parent.addMarkers();
example: basic example
In your case, The directive that wants to access the parent is again called from inside another directive,hence for such cases you can use $parent.$parent,
scope.$parent.$parent.addMarkers(); as shown in the following:
example:example for your case
This can be done if the number of directives using the parent scope is limited. If the hierarchy is long, then using multiple $parent makes the code clumsy. In those cases, it is preferable to add the parent method inside a service and call the service itself from the directives.
Example: service example
I should use $rootScope instead of $scope like below,
$rootScope.addMarkers = function(item){
alert("Helloo");
$scope.markers = itemProvider.addMarkers();
}
I have the following in a controller:
Restangular.all('foos').getList().then(
function(foos) {
$scope.foos = foos;
});
In an HTML page, I am able to do the following:
<div ng-repeat="foo in foos | orderBy:'fooName'">
I want to move the ng-repeat to a directive, so I have the following in a directive:
app.directive('interactionFoos', function(){
return {
restrict: 'A',
scope : false,
link: function($scope, $element, $attrs) {
//console.log("*** size: " + $scope.foos.length);
}
}
});
And in the HTML I will have:
<div interaction-foos></div>
In the directive, I am getting undefined for $scope.foos.
As a test, in the controller, I hard coded: $scope.test= 'foobar'.
Then, in the directive, I replaced the log line with the following and it printed 'foobar':
console.log("*** test: " + $scope.test);
I do not know why $scope.test is working as I expect, but $scope.foos is not?
I believe this is an Async issue as Restangular would run as a promise so foo would not be set when the directive link function runs. To get around this you need to add a watch to see when the scope has changed
$scope.$watch('foos', function(newValue, oldValue) { console.log(newValue, oldValue); });
do something like this:
angular.module("app", [])
.controller("interactionCtrl", function($scope) {
$scope.foos = ["A", "B", "C"];
})
.directive("interactionFoos", function() {
return {
restrict: 'A',
scope: {
foos: '='
},
link: function(scope, element, attrs) {
alert(scope.foos.length);
}
};
});
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
<div ng-app="app" ng-controller="interactionCtrl">
<div interaction-foos foos="foos"></div>
</div>
basically the foos in the directive binds to the foos that are exposed on the controller.
also in your linking function don't use $ in the name of the function arguments, for example call the first argument scope instead of scope, because those arguments are not really injected to the link function (it's a regular function call, if you name the first argument bob it will still be equal to the scope)