Calling Parent Controller From Angular Directive - angularjs

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

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: '='
}

Call method on Directive to pass data to Controller

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>

Function with args in parent scope is being binded but not being called in angular directive

I'm trying to call a function that is in the parent scope of a directive but it is not being called.
We want to pass the parameter 'arg' that is being modified to the parent scope and do some logic to it like 'arg = arg + 1', but this function is not being called.
function MyCtrl($scope) {
$scope.arg = 0
$scope.myFunction = function(arg) {
//do some logic like '+ 1'
arg++;
$scope.arg = arg;
}
}
myApp.directive('directive', function () {
return {
restrict: 'E',
scope: {
arg: '=arg',
fun: '&'
},
template: '<input ng-model="arg"/>',
link: function (scope) {
scope.$watch('arg', function (arg) {
scope.fun(arg);
});
}
};
});
Here is a little fiddle with a basic use case. http://jsfiddle.net/HB7LU/2677/
The $scope function isn't being called because you are parsing it wrong.
To parse a function you need to assign it like so:
<directive arg="arg" fun="myFunction()"></directive>
If you want to pass arguments to it, you need to be just a little more careful. First you need to describe the arguments in advance like so:
<directive arg="arg" fun="myFunction(arg)"></directive>
and when you're calling the function inside the directive you apply it like so:
scope.fun({arg: arg});
I created a jsFiddle that solves your issue: jsFiddle
Please note that your own fiddle had a recursive call in it: everytime you increase arg you also trigger the $watch and so it went into a loop. I changed that a bit (since I know you made it just to show your example.
If you want to learn a bit more about the & parsing in directives, check out this video which I highly recommend: egghead.io isolate scope &

Controller Required By Directive Can't Be Found

I have a directive that I'd like another directive to be able to call in to. I have been trying to use directive controllers to try to achieve this.
Directive one would be sitting on the same page as directive two, and directive one would call methods exposed by directive two's controller:
Directive 1:
'use strict';
angular.module('angularTestApp')
.directive('fileLibrary', function () {
return {
templateUrl: 'views/manage/file_library/file-library.html',
require: 'videoClipDetails',
restrict: 'AE',
link: function postLink(scope, element, attrs, videClipDetailsCtrl) {
scope.doSomethingInVideoClipDirective = function() {
videClipDetailsCtrl.doSomething();
}
}
};
});
Directive Two:
'use strict';
angular.module('angularTestApp')
.directive('videoClipDetails', function () {
return {
templateUrl: 'views/video_clip/video-clip-details.html',
restrict: 'AE',
controller: function($scope, $element) {
this.doSomething = function() {
console.log('I did something');
}
},
link: function postLink(scope, element, attrs) {
console.log('videoClipDetails directive');
//start the element out as hidden
}
};
});
File where the two are used and set up as siblings:
<div>
<div video-clip-details></div>
<!-- main component for the file library -->
<div file-library></div>
</div>
I know reading documentation I picked up that the controllers can be shared when the directives are on the same element, which makes me think I might be looking at this problem the wrong way. Can anyone put me on the right track?
From the angular.js documentation on directives
When a directive uses require, $compile will throw an error unless the specified controller is found. The ^ prefix means that this directive searches for the controller on its parents (without the ^ prefix, the directive would look for the controller on just its own element).
So basically what you are trying to do with having siblings directly communicate is not possible. I had run into this same issue but I did not want to use a service for communication. What I came up with was a method of using a parent directive to manage communication between its children, which are siblings. I posted the example on github.
What happens is that both children require the parent (require: '^parentDirective') and their own controller, both of which are passed into the link function. From there each child can get a reference to the parent controller and all of its public methods, as an API of sorts.
Below is one of the children itemEditor
function itemEditor() {
var directive = {
link: link,
scope: {},
controller: controller,
controllerAs: 'vm',
require: ['^itemManager', 'itemEditor'],
templateUrl: 'app/scripts/itemManager/itemManager.directives.itemEditor.html',
restrict: 'A'
};
return directive;
function link(scope, element, attrs, controllers) {
var itemManagerController = controllers[0];
var itemEditorController = controllers[1];
itemEditorController.itemManager = itemManagerController;
itemEditorController.initialize();
}
function controller() {
var vm = this;
// Properties
vm.itemManager = {};
vm.item = { id: -1, name: "", size: "" };
// Methods
vm.initialize = initialize;
vm.updateItem = updateItem;
vm.editItem = editItem;
// Functions
function initialize() {
vm.itemManager.respondToEditsWith(vm.editItem);
}
function updateItem() {
vm.itemManager.updateItem(vm.item);
vm.item = {};
}
function editItem(item) {
vm.item.id = item.id;
vm.item.name = item.name;
vm.item.size = item.size;
}
}
}
Note how the values passed into the require array are the parent directive's name and the current directive's name. These are then both accessible in the link function via the controllers parameter. Assign the parent directive's controller as a property of the current child's and then it can be accessed within the child's controller functions via that property.
Also notice how in the child directive's link function I call an initialize function from the child's controller. This is where part of the communication lines are established.
I'm basically saying, anytime you (parent directive) receive a request to edit an item, use this method of mine named editItem which takes an item as a parameter.
Here is the parent directive
function itemManager() {
var directive = {
link: link,
controller: controller,
controllerAs: 'vm',
templateUrl: 'app/scripts/itemManager/itemManager.directives.itemManager.html',
restrict: 'A'
};
return directive;
function link(scope, element, attrs, controller) {
}
function controller() {
var vm = this;
vm.updateMethod = null;
vm.editMethod = null;
vm.updateItem = updateItem;
vm.editItem = editItem;
vm.respondToUpdatesWith = respondToUpdatesWith;
vm.respondToEditsWith = respondToEditsWith;
function updateItem(item) {
vm.updateMethod(item);
}
function editItem(item) {
vm.editMethod(item);
}
function respondToUpdatesWith(method) {
vm.updateMethod = method;
}
function respondToEditsWith(method) {
vm.editMethod = method;
}
}
}
Here in the parent you can see that the respondToEditsWith takes a method as a parameter and assigns that value to its editMethod property. This property is called whenever the controller's editItem method is called and the item object is passed on to it, thus calling the child directive's editItem method. Likewise, saving data works the same way in reverse.
Update: By the way, here is a blog post on coderwall.com where I got the original idea with good examples of require and controller options in directives. That said, his recommended syntax for the last example in that post did not work for me, which is why I created the example I reference above.
There is no real way with require to communicate between sibling elements in the way you are trying to do here. The require works the way you have set up if the two directives are on the same element.
You can't do this however because both of your directives have an associated templateUrl that you want to use, and you can only have one per element.
You could structure your html slightly differently to allow this to work though. You basically need to put one directive inside the other (transcluded) and use require: '^videoClipDetails'. Meaning that it will look to the parent to find it.
I've set up a fiddle to demonstrate this: http://jsfiddle.net/WwCvQ/1/
This is the code that makes the parent thing work:
// In videoClipDetails
template: '<div>clip details<div ng-transclude></div></div>',
transclude: 'true',
...
// in markup
<div video-clip-details>
<div file-library></div>
</div>
// in fileLibrary
require: '^videoClipDetails',
let me know if you have any questions!

Accessing parent directive's controller recursively in AngularJS

I need to get parent's controller, so my directive has a require property, as follows:
module.directive('tag', function () {
return {
require: '?^tag',
restrict: 'E',
controller: function () {
this.payload = getPayload();
},
link: function (scope, element, attrs, ctrl) {
usePayload(ctrl.payload);
}
};
});
However the ctrl parameter of the link function returns the controller of the current directive, not the parent's one. AngularJS documentation is clear about this:
?^ - Attempt to locate the required controller by searching the element's parents, or return null if not found.
What am I doing wrong?
Either the docs or the code are misleading here... require with ^ looks at the current element and then all parent, using the inheritedData method (see https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L942). So you won't be able to require a directive with the same name from a parent using this approach.
When I've had this issue in the past I've looked at the form directive which needs to do what you are asking. Its controller method grabs the parent like so (https://github.com/angular/angular.js/blob/master/src/ng/directive/form.js#L39):
controller: function($element) {
var parentForm = $element.parent().controller('form');
}
Taking this, you should be able to call element.parent().controller('tag') to find the parent controller either in the controller or postLink methods.

Resources