this is slightly different to the other questions on stack in that, I need to call my controller's functions with parameters that exist exclusively in my directive.
directive (markup):
<div do-something="doSomething(x)"></div>
directive (js):
var directive = function () {
return {
restrict: 'EA',
templateUrl: '/angular/node-for-detail.html',
scope: {
doSomething: '&'
}
}
};
markup inside directive:
<span ng-click="doSomething('im not resolving')"></span>
function inside controller:
$scope.doSomething = function(myVar) { //myVar is null };
So the problem is, inside the directive param.abs resolved fine, but then, inside the called scope function the parameters are null! What am I doing wrong? How can I get the parameter to make it through?
You need to decide on a standard name for the parameter to the function that will be specified in the markup (in your example, you used x). Then you can create a function in the directive's isolate scope that will call the doSomething() method like so:
directive link function:
$scope.runCallback = function (param) {
$scope.doSomething({x: param});
}
Then just change your markup inside the directive to be:
<span ng-click="runCallback('im not resolving')"></span>
Now your markup call (<div do-something="doSomething(x)"></div>) and the function inside the controller will work correctly.
Try replace your markup inside directive be like this :
<span ng-click="doSomething()('im not resolving')"></span>
And directive markup :
<div do-something="doSomething"></div>
Related
I have - ng-view - template create item functionality and same template containing one directive that load the saved items.
Now, when i do save create item and immediately, its not refreshing list of items (from directive).
Can anyone tell me how I would resolve this, so, after saving item, immediately directive is refreshed.
Note: directive link function is making call to $http and retrieving data and populate in directive template. And directive element is added in other html template.
html template: (which has separate controller and scope).
<div>.....code</div>
<div class="col-md-12">
<parts-list></parts-list>
</div>
directive code:
(function () {
angular.module("application")
.directive("partsList", function (partService) {
return {
templateUrl: 'partsListView.html',
restrict: 'E',
scope: {},
link: function ($scope) {
$scope.partList = [{}];
RetrieveParts = function () {
$scope.partList=partService.RetrieveParts();
};
}
};
});
})();
For starters, your ReceiveParts variable doesn't have proper closure. Also, are you calling this function? I'm not sure where this function gets executed.
link: function ($scope) {
$scope.partList = [{}];
RetrieveParts = function () {
$scope.partList=partService.RetrieveParts();
};
}
An easy trick I've learned that makes it trivial to execute some of the the directives linking function logic in sync with angularjs's digest cycle by simply wrapping the logic I need in sync with the $timeout service ($timeout is simply a setTimeout call followed by a $scope.$apply()). Doing this trick would make your code look like:
link: function ($scope) {
$scope.partList = [{}];
$scope.fetchedPartList = false;
$timeout(function() {
$scope.partList = partService.RetrieveParts();
$scope.fetchedPartList = true;
});
}
Additionally, you'll notice the boolean value I set after the partList has been set. In your HTML you can ng-if (or ng-show/hide) on this variable to only show the list once it's been properly resolved.
I hope this helps you.
Use isolated scope in directive:
return {
templateUrl: 'partsListView.html',
restrict: 'E',
scope: {partList: '='},
and in template:
<parts-list partList="list"></parts-list>
Where list is where ui will update with updated data.
See how isolated scope using basic Example
This is my case -- I have directive with isolated scope and I would like to call function from parent's scope with mixed arguments. Mixed -- meaning, one argument comes from the directive, the other comes from the parent.
In case of arguments coming from directive, I could bind that function with < and use it, in case of arguments coming from parent's scope, I could bind entire function call with &.
I am thinking about two approaches -- one, would simulate currying, call the function with parent's arguments which would return a function accepting directive arguments. Second -- somehow introduce directive variables in the parent's scope, so I could write:
<my-directive on-alarm="emergency(parent_var,dir_var)"/>
I like the second one better. But I don't know how to do it, i.e. how to introduce directive variables into parent's scope -- without doing a manual "reverse" binding, like:
<my-directive for_sake_of_calling="dir_var" on-alarm="emergency(parent_var,dir_var)"/>
But those are more like my guesses -- the main question is: how to call parent's function with mixed arguments?
You can achieve this by doing the following:
First, setup up the main application HTML,
<body ng-app="app">
<div ng-controller="MainCtrl as vm">
Emergency text: {{vm.emergencyText}}
<my-directive on-alarm="vm.emergency(vm.parentVar, directiveVar)"></my-directive>
</div>
</body>
You'll notice that the on-alarm callback contains a reference to the vm.parentVar variable which just refers to MainCtrl.parentVar, and directiveVar which will come from the directive itself.
Now we can create our main controller:
angular.module('app', []);
angular
.module('app')
.controller('MainCtrl', function () {
// Initialise the emergency text being used in the view.
this.emergencyText = '';
// Define our parent var, which is a parameter called to the emergency function.
this.parentVar = 'This is an emergency';
// Define the emergency function, which will take in the parent
// and directive text, as specified from the view call
// vm.emergency(vm.parentVar, directiveVar).
this.emergency = function (parentText, directiveText) {
this.emergencyText = parentText + ' ' + directiveText;
}.bind(this);
});
Finally, we will create the directive.
angular
.module('app')
.directive('myDirective', function () {
return {
scope: {
onAlarm: '&'
},
link: function (scope, element, attrs) {
scope.onAlarm({ directiveVar: 'from myDirective' });
}
}
});
The magic happens after we call scope.onAlarm({ directiveVar: 'from myDirective' });. This call tells angular that the alarm callback function (emergency) will have access to directiveVar, which we referenced earlier in the view through on-alarm="vm.emergency(vm.parentVar, directiveVar)". Behind the scenes, angular will correctly resolve the parentVar scope to MainCtrl and the directiveVar scope to the directive through its $parse service.
Here's a full plunkr.
This is the controller of the main template:
app.controller('OverviewCtrl', ['$scope', '$location', '$routeParams', 'websiteService', 'helperService', function($scope, $location, $routeParams, websiteService, helperService) {
...
$scope.editWebsite = function(id) {
$location.path('/websites/edit/' + id);
};
}]);
This is the directive:
app.directive('wdaWebsitesOverview', function() {
return {
restrict: 'E',
scope: {
heading: '=',
websites: '=',
editWebsite: '&'
},
templateUrl: 'views/websites-overview.html'
}
});
This is how the directive is applied in main template:
<wda-websites-overview heading="'All websites'" websites="websites" edit-website="editWebsite(id)"></wda-websites-overview>
and this is method is called from directive template (website-overview.html):
<td data-ng-click="editWebsite(website.id)">EDIT</td>
QUESTION: When EDIT is clicked, this error appears in the console:
TypeError: Cannot use 'in' operator to search for 'editWebsite' in 1
Does anyone know what goes on here?
Since you defined an expression binding (&), you need to explicitly call it with an object literal parameter containing id if you want to bind it in the HTML as edit-website="editWebsite(id)".
Indeed, Angular needs to understand what this id is in your HTML, and since it is not part of your scope, you need to add what are called "locals" to your call by doing:
data-ng-click="editWebsite({id: website.id})"
Or as an alternative:
data-ng-click="onClick(website.id)"
With the controller/link code:
$scope.onClick = function(id) {
// Ad "id" to the locals of "editWebsite"
$scope.editWebsite({id: id});
}
AngularJS includes an explanation of this in its documentation; look for the example involving "close({message: 'closing for now'})" at the following URL:
https://docs.angularjs.org/guide/directive
TL;DR; - You are assuming that the bound function is being passed to the child component, as it would be in React. This is incorrect. In fact, AngularJS is parsing the string template and creating a new function, which then calls the parent function.
This generated function expects to receive an object with keys and values, rather than a plain variable.
Longer Explanation
This happens when you have bound a function using '&', and have tried to call that function from your controller, passing a plain variable rather than an object containing the name of the plain variable.
The object keys are needed by the templating engine to work out how to pass values into the bound function.
eg. you have called boundFunction('cats') rather than boundFunction({value: 'cats'})
Worked Example
Say I create a component like this:
const MyComponent = {
bindings: {
onSearch: '&'
},
controller: controller
};
This function (in the parent) looks like this:
onSearch(value) {
// do search
}
In my parent template, I can now do this:
<my-component on-search="onSearch(value)"></my-component>
The binding here will be parsed from the string. You're not actually passing the function. AngularJS is making a function for you which calls the function. The binding created in the template can contain lots of things other than the function call.
AngularJS somehow needs to work out where to get value from, and it does this by receiving an object from the parent.
In myComponent controller, I need to do something like:
handleOnSearch(value) {
if (this.onSearch) {
this.onSearch({value: value})
}
}
I have a form directive that uses a specified callback attribute with an isolate scope:
scope: { callback: '&' }
It sits inside an ng-repeat so the expression I pass in includes the id of the object as an argument to the callback function:
<directive ng-repeat = "item in stuff" callback = "callback(item.id)"/>
When I've finished with the directive, it calls $scope.callback() from its controller function. For most cases this is fine, and it's all I want to do, but sometimes I'd like to add another argument from inside the directive itself.
Is there an angular expression that would allow this: $scope.callback(arg2), resulting in callback being called with arguments = [item.id, arg2]?
If not, what is the neatest way to do this?
I've found that this works:
<directive
ng-repeat = "item in stuff"
callback = "callback"
callback-arg="item.id"/>
With
scope { callback: '=', callbackArg: '=' }
and the directive calling
$scope.callback.apply(null, [$scope.callbackArg].concat([arg2, arg3]) );
But I don't think it's particularly neat and it involves puting extra stuff in the isolate scope.
Is there a better way?
Plunker playground here (have the console open).
If you declare your callback as mentioned by #lex82 like
callback = "callback(item.id, arg2)"
You can call the callback method in the directive scope with object map and it would do the binding correctly. Like
scope.callback({arg2:"some value"});
without requiring for $parse. See my fiddle(console log) http://jsfiddle.net/k7czc/2/
Update: There is a small example of this in the documentation:
& 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 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
and 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}).
Nothing wrong with the other answers, but I use the following technique when passing functions in a directive attribute.
Leave off the parenthesis when including the directive in your html:
<my-directive callback="someFunction" />
Then "unwrap" the function in your directive's link or controller. here is an example:
app.directive("myDirective", function() {
return {
restrict: "E",
scope: {
callback: "&"
},
template: "<div ng-click='callback(data)'></div>", // call function this way...
link: function(scope, element, attrs) {
// unwrap the function
scope.callback = scope.callback();
scope.data = "data from somewhere";
element.bind("click",function() {
scope.$apply(function() {
callback(data); // ...or this way
});
});
}
}
}]);
The "unwrapping" step allows the function to be called using a more natural syntax. It also ensures that the directive works properly even when nested within other directives that may pass the function. If you did not do the unwrapping, then if you have a scenario like this:
<outer-directive callback="someFunction" >
<middle-directive callback="callback" >
<inner-directive callback="callback" />
</middle-directive>
</outer-directive>
Then you would end up with something like this in your inner-directive:
callback()()()(data);
Which would fail in other nesting scenarios.
I adapted this technique from an excellent article by Dan Wahlin at http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters
I added the unwrapping step to make calling the function more natural and to solve for the nesting issue which I had encountered in a project.
In directive (myDirective):
...
directive.scope = {
boundFunction: '&',
model: '=',
};
...
return directive;
In directive template:
<div
data-ng-repeat="item in model"
data-ng-click='boundFunction({param: item})'>
{{item.myValue}}
</div>
In source:
<my-directive
model='myData'
bound-function='myFunction(param)'>
</my-directive>
...where myFunction is defined in the controller.
Note that param in the directive template binds neatly to param in the source, and is set to item.
To call from within the link property of a directive ("inside" of it), use a very similar approach:
...
directive.link = function(isolatedScope) {
isolatedScope.boundFunction({param: "foo"});
};
...
return directive;
Yes, there is a better way: You can use the $parse service in your directive to evaluate an expression in the context of the parent scope while binding certain identifiers in the expression to values visible only inside your directive:
$parse(attributes.callback)(scope.$parent, { arg2: yourSecondArgument });
Add this line to the link function of the directive where you can access the directive's attributes.
Your callback attribute may then be set like callback = "callback(item.id, arg2)" because arg2 is bound to yourSecondArgument by the $parse service inside the directive. Directives like ng-click let you access the click event via the $event identifier inside the expression passed to the directive by using exactly this mechanism.
Note that you do not have to make callback a member of your isolated scope with this solution.
For me following worked:
in directive declare it like this:
.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
scope: {
myFunction: '=',
},
templateUrl: 'myDirective.html'
};
})
In directive template use it in following way:
<select ng-change="myFunction(selectedAmount)">
And then when you use the directive, pass the function like this:
<data-my-directive
data-my-function="setSelectedAmount">
</data-my-directive>
You pass the function by its declaration and it is called from directive and parameters are populated.
I am trying to build generic code as much as possible.
So I'm having 2 directives, one nested inside the other while I want the nested directive to call a method on the main controller $scope.
But instead it requests the method on the parent directive, I want to know how to execute a method against the main controller scope instead of the parent directive.
Here is a sample code for my issue
My HTML should look something like this:
<div ng-controller='mainctrl'>
<div validator>
<div datepicker select-event='datepickerSelected()'/>
</div>
</div>
Javascript:
var app = angular.module("app",[]);
var mainctrl = function($scope){
$scope.datepickerSelected = function(){
//I WANT TO ACCESS THIS METHOD
}
}
app.directive("validator",function(){
return {
scope : {
//the datepicker directive requests a datepickerSelected() method on this scope
//while I want it to access the mainctrl scope
}
link: function(scope){
//some code
}
}
});
app.directive("datepicker", function(){
return{
scope: {
selectEvent: '&'
}
link: function(scope, elem){
//example code
$(elem).click(scope.selectEvent); //want this to access mainctrl instead validator directive
}
}
});
Simply remove the validator directive's scope property, thus eliminating its isolated scope. That means that validator will have the same scope that it is nested in (your controller) and datepicker will use that scope.
Another option if you want both to have isolated scopes (doesn't sound like you do) is to pass the function through to "validator's" scope.