Call method on Directive to pass data to Controller - angularjs

So basically I have a controller, which lists a bunch of items.
Each item is rendering a directive.
Each directive has the ability to make a selection.
What I want to achieve is once the selection has been made, I want to call a method on the controller to pass in the selection.
What I have so far is along the lines of...
app.directive('searchFilterLookup', ['SearchFilterService', function (SearchFilterService) {
return {
restrict: 'A',
templateUrl: '/Areas/Library/Content/js/views/search-filter-lookup.html',
replace: true,
scope: {
model: '=',
setCriteria: '&'
},
controller: function($scope) {
$scope.showOptions = false;
$scope.selection = [];
$scope.options = [];
$scope.selectOption = function(option) {
$scope.selection.push(option);
$scope.setCriteria(option);
};
}
};
}]);
The directive is used like this:
<div search-filter-lookup model="customField" criteria="updateCriteria(criteria)"></div>
Then the controller has a function defined:
$scope.updateCriteria = function(criteria) {
console.log("Weeeee");
console.log(criteria);
};
The function gets called fine. But I'm unable to pass data to it :(

Try this:
$scope.setCriteria({criteria: option});
When you declare an isolated scope "&" property, angular parses the expression to a function that would be evaluated against the parent scope.
when invoking this function you can pass a locals object which extends the parent scope.
It's a common mistake to think that $scope.setCriteria is the same as the function inside the attribute. If you log it you'll see it's just an angular parsed expression function which have the parent scope saved at it's closure.
So when you run $scope.setCriteria() you actually evaluate an expression against the parent scope.
In your case this expression happens to be a function but it could be any expression.
But you don't have a criteria property on the parent scope, that's why angular let you pass a locals object to extend the parent scope. e.g. {criteria: option}
Extends the parent scope
you wrote in a comment that it requires the directive to have knowledge of the parameter name defined in the controller. No it doesn't, it just extends the parent scope with a criteria option, you can still use any expression you want though you are provided with an extra property you may use.
A good example would be ngEvents, take ng-click="doSomething($event)":
ngClick provides you with a local property $event, you don't have to use but you may if you need.
the directive doesn't know anything about the controller, it's up to you to decide which expression you write, cheers.

You can pass the function in using =...
scope: {
model: '=',
setCriteria: '='
},
controller: function($scope) {
// ...
$scope.selectOption = function(option) {
$scope.selection.push(option);
$scope.setCriteria(option);
};
}
<div search-filter-lookup model="customField" criteria="updateCriteria"></div>

Related

Can I have attributes in an Angular directive which uses parent scope?

I have a directive that uses the data from the parent scope. Now, I want to have an attribute in it, but still want to use the parent scope and not transfer all the other data in more attributes and duplicate objects.
var myDirective = function () {
return {
restrict: 'A',
templateUrl: '/Areas/GetAdvice/Templates/Directives/loan-comparison-table-directive.html?1',
//scope: - Don't add anything here to use the scope of the parent
// But how to use an attribute in this way?
controller: [
'$scope', function ($scope) {
// Some Code
}
}
So, how can I add an attribute (independent variable) in the directive, when having the parent scope inherited?
EDIT:
I want just a simple boolean property - something like isDescriptionVisible (true/false). The problem is that I use the same directive twice in my view, so I cannot have a variable in the parent scope, because it will affect both directives and I won't have control on them separately.
You need to create an isolated scope for the directive. This is the exact use case for it. Otherwise you need to create separate variables to track the booleans in the parent scope.
If you don't need to pass boolean value from the parent scope you can do the following:
var myDirective = function () {
return {
restrict: 'A',
templateUrl: '/Areas/GetAdvice/Templates/Directives/loan-comparison-table-directive.html?1',
scope: {}
controller: [
'$scope', function ($scope) {
$scope.isVisible = false;
}
}
If you need to pass it from the parent scope just simply replace
scope: {}
with
scope: {
isVisible: '='
}

ng-repeat overrides property in function but not in view model

I am new to angular and have a problem using my custom directive with ng-repeat. I want to display some posts I get from a rest interface and then use their _id property inside the directive for other purposes. However, it turns out that the property is always the one from the last displayed post when used from inside a function (test in the sample below). When trying to display the id directly over the viewmodel it shows the right one. Hope this makes sense. Any help would be appreciated.
//directive.js
angular.module('app').directive('gnPost', myGnPost);
function myGnPost() {
return {
restrict: 'E',
controller: 'postCtrl',
controllerAs: 'postCtrl',
bindToController: {
post: '='
},
scope: {},
templateUrl: 'template.html'
};
};
//controller.js
angular.module('app').controller('postCtrl', myPostCtrl);
function myPostCtrl(postRestService) {
vm = this;
vm.test = function () {
return vm.post._id;
};
};
// template.html
<p>{{postCtrl.post._id}}</p>
//displays the right id
<p>{{postCtrl.test()}}</p>
//displays the id of the last element of ng-repeat
//parent page.html
<gn-post ng-repeat="singlePost in posts.postList" post="singlePost"></gn-post>
In your controller, you have the following line:
vm = this;
It should be:
var vm = this;
By omitting the var, there is a vm variable created on the global scope instead of a local one per controller instance. As a result each iteration when vm.test is called, it's always pointing at the function defined on the last controller.
Fiddle - try including/omitting the var in postCtrl
It's good practice to use strict mode in Javascript to prevent that issue and others. In strict mode, it impossible to accidentally create global variables, as doing so will throw an error and you'll see the problem straight away. You just need to add this line at the start of your file or function:
"use strict";

angularJS Dynamic scope Definition for an isolated scope custom Directive Definition

I have an isolated scope custom directive say
<my_isolate_scope_dir columns="columns" />
And the directive Definition goes something like this.
.directive("myIsolateScopeDir", ["$compile","$templateCache",function($compile,$templateCache){
return {
restrict: 'E',
scope: {
columns: '=',
},
templateUrl: 'customdirective.html',
}
}]);
The question is ,in the directive definition scope:{} can i define one more scope variable dynamically whose value is coming from the parent scope variable columns.
The parent controller can be
$scope.columns=[{"name":"x","isLink":true,"onClickFunction":"clickedx"},{"name":"y","isLink":true,"onClickFunction":"clickedy"}]
$scope.clickedx=fucntion()
{
console.log("x is clicked");
}
i want my custom directive scope definition to be
scope: {
columns: '=',
clickedx: '&', //this is added dynamically to the definition based on the value in columns array
clickedy: '&' //this is added dynamically to the definition based on the value in columns array
},
let me know if this can be achieved in the same way or is there any other simpler way to do this.
You can manually inject the methods into the scope using $parse.
link: function (scope, element, attrs) {
if (scope.columns) {
scope.columns.forEach(function (column) {
if (column.onClickFunction) {
// column.onClickFunction is a string(eg, clickedx), we need to treat this as a function in scope
var fn = column.onClickFunction + "()";
// get a reference to the function defined in the scope
var getParentMethod = $parse(fn);
// Use of Locals: apart from accessing the properties of the scope, you will be able to pass few properties using locals, which will have the precedence over the properties of scope
// For example if the markup is -- <button on-click="test(arg1, arg2)"
// arg1, arg2 needs to be evaluated in the scope
// As isolateScope breaks the inheritance chain, we need to pass element.scope() to the parser function to access the arg1, arg2 properties from controllers scope
// define a method in the scope with the value of column.onClickFunction (eg, clickedx)
scope[column.onClickFunction] = function (locals) {
// when scope.clicedx is executed execute the method defined in the controller.
return getParentMethod(element.scope(), locals);
}
}
})
}
}

Calling Parent Controller From Angular Directive

In short I'm trying to pass data set in my directive back to its parent controller. I've seen several nuances of this but somewhere I'm missing something and what I'm doing isn't quite working. I have a directive that receives a call back definition from the parent like so:
<my-directive my-list="myData" update-parent="parentCallback(newData)">
The callback in the parent controller is pretty simple but the incoming data is null:
$scope.parentCallback = function(newData) { /* when it gets here its null */ };
The definition of the directive is like so:
myModule.directive('myDirective', function() {
return {
restrict: 'E',
templateUrl: 'Template.html',
replace: true,
scope: { myData: '=', updateParent: '&' },
controller: function($scope, $element, $attrs) {...}
A selection event in the directive's controller attempts to push data back through the callback like so:
$scope.updateSelection = function() {
/* here the data is good */
$scope.updateParent($scope.currentSelection);
};
The current selection variable is defined in the scope for the directive and set by selection on a drop down list box. What is happening is that in the function above it invokes the parent callback, finds it okay, passes in good data, but then when it hits the parent side the arguments are null. I little new to this but I thought the '&' would provide for me passing data back up to the parent. Any help would be greatly appreciated!
In order to pass the arguments to a bound function the arguments have to be passed as an object, were the keys of the object are the parameter names of the bound function, like this:
$scope.updateSelection = function() {
$scope.updateParent({newData:$scope.currentSelection});
};

Why doesn't scope pass through properly to nested directives?

In this example plunker, http://plnkr.co/edit/k2MGtyFnPwctChihf3M7, the nested directives compile fine when calculating the DOM layout, but error when the directive tries to reference a variable to bind to and says the variable is undefined. Why does this happen? The data model I am using is a single model for many nested directives so I want all nested directives to be able to edit the top level model.
I havn'et got a clue as to what you're trying to do. However, your comment 'so I want all nested directives to be able to edit the top level model' indicates you want your directive to have scope of your controller. Use
transclude = true
in your directive so that your directives can have access to your the parent scope.
http://docs.angularjs.org/guide/directive#creating-a-directive-that-wraps-other-elements
I don't know why you are doing it this way exactly, it seems like there should be a better way, but here goes a stab at getting your code working. First you create an isolated scope, so the scopes don't inherit or have access to anything but what is passed in the data attribute. Note that you can have your controller set dumbdata = ... and say <div data="dumbdata" and you will only have a data property on your isolated scope with the values from dumbdata from the parent in the data property. I usually try to use different names for the attribute and the data I'm passing to avoid confusion.
app.directive('project', function($compile) {
return {
template: '<div data="data"></div>',
replace: true,
scope: {
data: '=' // problem
},
Next, when you compile you are passing variables as scopes. You need to use real angular scopes. One way is to set scope: true on your directive definition, that will create a new child scope, but it will inherit from the parent.
app.directive('outer', function($compile) {
var r = {
restrict: 'A',
scope: true, // new child scope inherits from parent
compile: function compile(tEle, tAttr) {
A better way is probably to create the new child scope yourself with scope.$new(), and then you can add new child properties to pass for the descendants, avoiding the problem of passing values as scopes and still letting you have access to the individual values you're looping over (plunk):
app.directive('outer', function($compile) {
var r = {
restrict: 'A',
compile: function compile(tEle, tAttr) {
return function postLink(scope,ele,attrs) {
angular.forEach(scope.outer.middles, function(v, i) {
var x = angular.element('<div middle></div>');
var s = scope.$new(); // new child scope
s.middle = v; // value to be used by child directive
var y = $compile(x)(s); // compile using real angular scope
ele.append(y);
});
};
}
};
return r;
});

Resources