Angular: Bind callback function using & and pass-in arguments - angularjs

I have a (simplified) directive
angular.module('myApp')
.directive('myButton', function () {
return {
restrict: 'E',
scope: {
callbackFn: '&'
},
template: '<button ng-click=ca;;backFn($evenb)'
}
});
Now, in some parent controller I have defined a callback function:
this.myCallback = function ($event) {
this.doIt($event);
}
and the HTML:
<my-button callback-fn="page.myCallback()"></my-button>
(I'm using things like bindToController and controllerAs)
The issue is that the $event is never passed to myCallback, which most likely has to do with how I bind this function (&). But on the other hand, inside myCallback I would like to use this.
Is there some way to fix this ? without doing things like
var self = this;
this.myCallback = function ($event) {
self.doIt($event);
}

You haven't completely set up your bindings correctly. You can pass back arguments from the directive to the parent controller via a key-value map. According to the angular docs (emphasis mine):
& or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given <widget my-attr="count = count + value"> and widget definition of scope: { localFn:'&myAttr'}, then isolate scope property localFn will point to a function wrapper for the count = count + value expression. Often it's desirable to pass data from the isolated scope via an expression to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment(amount) then we can specify the amount value by calling the localFn as localFn({amount: 22}).
So that means in your consuming HTML you need to add parameters:
<my-button callback-fn="page.myCallback(parentEvent)"></my-button>
And then in the directive:
......
restrict: 'E',
scope: {
callbackFn: '&'
},
template: '<button ng-click="ctrl.callbackFn({parentEvent: $event})">Callback</button>'
,

According to me you should do it this way :
In your HTML page :
<my-button callback-fn="page.myCallback(event)"></my-button>
In your directive :
angular.module('myApp')
.directive('myButton', function () {
return {
restrict: 'E',
controller: 'Controller',
bindToController: true,
scope: {
callbackFn: '&'
},
template: '<button ng-click=foo($event)'
}
});
function Controller() {
this.foo = function (event) {
this.callbackFn({event: event});
}
}
But I'm not sur what's the point of your question.

Related

angularjs: can' t pass parameter to the parent controller method via '&' binding in isolated directive scope

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});

Directive scope not getting updated in controller

My directive's controller is not getting updated with the scope that was set using the '=' two-way-binding.
Here is my directive:
.directive('navigation', function() {
return {
restrict: 'E',
scope: {
selection: '=?selectedItem',
goforward: '&onForward'
},
controller: function() {
var vm = this;
vm.hideForward = !vm.selection
},
controllerAs: 'vm',
bindToController: true,
template: '<button ng-hide="vm.hideForward" ng-click="vm.goforward()">Continue</button>'
};
});
Here is my html file where I use the directive:
<div class='product' ng-click='ctrl.selectedItem = true'>item</div>
<navigation on-forward="ctrl.goForward()" selected-item='ctrl.selectedItem'></navigation>
note: the ctrl.goForward() works just fine.
The vm.selectedItem in the html's controller is only set to true once the product div is clicked.
I expected the ctrl.selectedItem to get passed into my directive's controller and modify the vm.hideForward value, except this is not happening.
I want to be able to change whether the navigation directive is visible and/or active depending on variables that are passed into it from whatever controller's scope I used my directive in.
If I place a <div>{{vm.selectedItem}}</div> inside my directive's template, that does print out properly depending on how ctrl.selectedItem that value changes. My issue is getting the directive's controller to change as well.
How am I setting up this scope binding improperly? I am using angular 1.5.3
You dont need the double brackets for binding a function to ng-click, use ng-click="vm.goforward()"
Pass the function to the directive as on-forward="ctrl.goForward", if you use parenthesis you will be passing the result of the function call instead.
Also for, ng-click='ctrl.selectedItem === true' you should use ng-click='ctrl.selectedItem = true' to set the value, as === is a comparison operator.
ctrl.selectedItem seems to be a variable from the present controller. So while passing it as attribute, you need to pass it as '{{ctrl.selectedItem}}" .
Try using:
**<navigation on-forward="ctrl.goForward()" selected-item='{{ctrl.selectedItem}}'></navigation>**
Try this
.directive('navigation', function() {
return {
restrict: 'E',
scope: {
selection: '=selectedItem',
goforward: '&onForward'
},
controller: function(scope) {
var vm = this;
vm.hideForward = !scope.selection
},
controllerAs: 'vm',
bindToController: true,
template: '<button ng-hide="vm.hideForward" ng-click="vm.goforward()">Continue</button>'
};
});

How to bind a method in a custom directive to the parent scope in angularjs

First foray into custom directives and getting a bit stuck on binding to a method in the parent scope.
So when I use my custom directive in my app:
<dropdown x-label="Stuff" x-divider="-"
x-list = "listOfStuff"
x-ng-model="id"
x-change-select="controllerMethodToBeCalled(id)">
</dropdown>
The template is as follows and triggers the 'update' function in the directive controller on ng-change.
<div class="dropdown_container">
<div class="select_label">{{label}}</div>
<div class="select_divider">{{divider}}</div>
<select class="dropdown_select" id="dropdownList"
ng-model="ngModel"
ng-options="option.id as option.name for option in list | orderBy:'name'"
ng-change="update(ngModel)">
</select>
</div>
The directive code is below and I can call the method in parent scope directly whcih works fine but I'd like to trigger the 'change-select' that I've bound function 'controllerMethodToBeCalled' passing the id. Otherwise the component is not truly self contained.
angular.module('myApp.component.dropdown', [])
.directive('dropdown', function() {
return {
restrict: 'E',
require: "^ngModel",
replace: true,
scope: {
list: "=",
ngModel: '=',
changeSelect: '&'
},
templateUrl: 'component/dropdown/dropdown.tpl.html',
link: function ($scope, element, attrs) {
attrs.$observe('label', function (value) {
$scope.label = value;
});
attrs.$observe('divider', function (value) {
$scope.divider = value;
});
},
controller: function($scope){
$scope.update = function(id){
//replace this line with call to changeSelect passing id
$scope.$parent.controllermethodToBeCalled(id);
};
}
};
});
Probably very easy to fix but I just can't see it. Any ideas/suggestions?
You only need to call $scope.changeSelect(). See this Plunker. If you don't want to use the id from a parent scope, but rather pass the id argument from the function, you can do that do:
$scope.update = function(id){
$scope.changeSelect({id: id});
};
It basically says use the value of the local variable id for id in the expression controllerMethodToBeCalled(id).

How do I pass a parameter to a controller from a directive?

My HTML looks like this
<td currency-convert idrVal="{{(btcAsk.btcValue * btcAsk.btcAmnt).toFixed(2)}}"></td>
The AngularJS code
The controller has the method:
$scope.convertIDRtoUSD = function(idrValue) {
return CurrencyConversions.convertToUSD(idrValue, 'IDR');
};
And the directive looks like..
bitcoinApp.directive("currencyConvert", function() {
return {
restrict: 'A',
scope: {
idrval: '#'
},
template: '<span class="has-tip" tooltip="convertIDRtoUSD({{idrval}})" tooltip-animation="false">{{idrval}}</span>'
};
});
This is currently not calling the convertIDRtoUSD method.
Reading online, I think I'm supposed to use the Isolate Scope "&" but have not had success so far.
I'll try to word this as best I can, so bear with me! Your template contains a method. At run time, it expects this method to be present on the current scope. However, at that point, the scope is the isolated scope you created with the directive, and simply contains the value of idrval, so that method is undefined.
You need to either add the function convertIDRtoUSD() to the scope of the directive, or pass the function into your directive along with idrval. If you choose the former, you'r directive might look like this:
bitcoinApp.directive("currencyConvert", function() {
return {
restrict: 'A',
scope: {
idrval: '#'
},
template: '<span class="has-tip" tooltip="convertIDRtoUSD({{idrval}})" tooltip-animation="false">{{idrval}}</span>',
link: function(scope) {
scope.convertIDRtoUSD = function(idrValue) {
return CurrencyConversions.convertToUSD(idrValue, 'IDR');
};
}
};
});
If you want to pass the function in, and the function exists on the controller, your html would look something like this:
<td currency-convert my-func="convertIDRtoUSD(val)" idrVal="{{(btcAsk.btcValue * btcAsk.btcAmnt).toFixed(2)}}"></td>
And your directive:
bitcoinApp.directive("currencyConvert", function() {
return {
restrict: 'A',
scope: {
idrval: '#',
myFunc: '&'
},
template: '<span class="has-tip" tooltip="myFunc({val: idrval})" tooltip-animation="false">{{idrval}}</span>'
};
});
Some things to note - the attribute name of the function should be 'dash-named' not camel-case named, and parameters to the function passed into the directive have to be passed an an object with named values.
Hope this helps!

How to use isolated scope action with parameter from one directive to another

I have a directive I want to pass isolated scoped action with parameter from one directive to another. please see the Plunker.
plnkr
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.singleClick = function (test) {
alert('singleClick'+test);
}
});
app.directive('myButton', [function () {
return {
restrict: 'E',
template: '<input type="button" value="Click" ng-click="click("test")" />',
scope: { onSingleclick: '&singleclickFn' },
link: function (scope, iElm, iAttrs, controller) {
scope.click = function (test) {
alert('singleClick'+test);
scope.onSingleclick(test);
}
}
};
}]);
app.directive('myNewButton', [function () {
return {
restrict: 'E',
scope: { singleclick: '&singleclickFn' },
template: '<my-button singleclick-fn="singleclick(test)" />',
};
}]);
In controller method I have got parameter is undefined.
To pass data from directive with isolated scope to the parent scope pass an object as argument instead of the primitive. So in your myButton directive use something like
ng-click="click({inp:'test'})
and access the argument as object in the singleClick function of your controller.
In your plunkr i can see you pass the result of the function execution to you directive instead of the function itself. Therefore it is undefined as the function doesn't return anything.
use singleclick-fn="singleClick" to pass the funciton to your directive's scope.
Please make your plunkr work (I get errors in the console if i open it), so i can verify the error

Resources