I have 3 nested, isolate scope, directives (see CodePen here) and I am able to pass a function (that takes an argument) from the outermost directive to the innermost directive (passing the function from outer directive to intermediate directive to inner directive).
What I'm failing to understand is what needs to be done to pass the argument from the inner directive back through intermediate directive back to the outer directive.
Again, see the CodePen example.
Note: Given only 2 isolate scope directives I can get this to work with something similar to the following...
angular.module('myApp', [])
.directive('myDir1', function() {
return {
template: '<div><my-dir-2 add-name="addName(name)"></my-dir-2></div>',
controller: function($scope) {
$scope.addName = function(name) {
alert(name); // alerts 'Henry'
};
}
}
})
.directive('myDir2', function() {
return {
scope: {
addName: '&'
},
template: "<span ng-click='addName({name: testName})'>Click to Add {{testName}}!</span>",
controller: function($scope) {
$scope.testName = 'Henry';
}
}
});
The above code gives me an Alert box with 'Henry' (just like I'd expect).
It's when I add an third, intermediate, isolate scope directive that I run into problems...
angular.module('myApp', [])
.directive('myDir1', function() {
return {
template: '<div><my-dir-2 add-name="addName(name)"></my-dir-2></div>',
controller: function($scope) {
$scope.addName = function(name) {
alert(name); // alerts 'Henry'
};
}
}
})
.directive('myDir2', function() {
return {
scope: {
addName: '&'
},
template: '<div><my-dir-3 add-name="addName({name: testName})"></my-dir-3></div>',
}
})
.directive('myDir3', function() {
return {
scope: {
addName: '&'
},
template: "<span ng-click='addName({name: testName})'>Click to Add {{testName}}!</span>",
controller: function($scope) {
$scope.testName = 'Henry';
}
}
});
This code gives me an alert box with undefined
...
A common misconception is that "& is for passing functions". This isn't technically correct.
What &
does is create a function on the directive scope that, when called, returns the result of the expression evaluated against the parent scope.
This function takes an object as an argument that will override local variables in the expression with those from the directive scope (the {name: testName}
) object in this case.
If you were to look under the hood, the $scope.addName
method in myDir2
would look like this (simplified):
$scope.addName = function(locals) {
return $parse(attr.addName)($scope.$parent, locals);
}
Your second directive works because the expression it is binding to is
addName(name)
This expression has a local variable name
, that is overridden with the value of testName from the directive when executed with
addName({name: testName}) //in the directive.
Remember - the addName
function in myDir2
IS NOT the same as the addName
function in myDir1
. It is a new function that evaluates the expression
addName(name)
against the parent scope and returns the result.
When you apply this logic to myDir3
, the expression that is evaluated is:
addName({name: testName})
Note that the only local variable in this expression is "testName". So when you call in myDir3
with
addName({name: testName})
there is no local variable name
to override, and testName
is left undefined.
Phew! No wonder this confuses JUST ABOUT EVERYBODY!
How to fix in your example:
You want the expressions to evaluate to the actual function in myDir1
.
angular.module('myApp', [])
.directive('myDir1', function() {
return {
template: '<div><my-dir-2 add-name="addName"></my-dir-2></div>',
controller: function($scope) {
$scope.addName = function(name) {
alert(name); // alerts 'Henry'
};
}
}
})
.directive('myDir2', function() {
return {
scope: {
addName: '&'
},
// addName() returns the actual function addName from myDir1
template: '<div><my-dir-3 add-name="addName()"></my-dir-3></div>',
}
})
.directive('myDir3', function() {
return {
scope: {
addName: '&'
},
//calls addName() on myDir2, which returns addName from myDir1, then invokes it passing testName as an argument
template: "<span ng-click='addName()(testName)'>Click to Add {{testName}}!</span>",
controller: function($scope) {
$scope.testName = 'Henry';
}
}
});
Here is the working Pen
Final note - the reason why '&'
is more appropriate than '='
here is that '='
is going to actually set up a $watch
and two-way bind the variables between the directives. This means that myDir2
could actually change the function appName
in myDir1
, which is not required and undesirable. It also requires setting up two $watch
s, which I try to avoid for performance reasons in Angular.
There are two ways to pass a function in an isolated scope. While '&' will make sure that what you are passing is in-fact a function, you can also pass a function as a bound variable with '=', and invoke it only when you need. This method has drawbacks, but it will leave the control of the invocation to the component that is in-charge of that invocation.
Your codepen working
angular.module('myApp', [])
.directive('myDir1', function() {
return {
template: '<div><my-dir-2 add-name="addName"></my-dir-2></div>',
controller: function($scope) {
$scope.addName = function(name) {
alert(name);
};
}
}
})
.directive('myDir2', function() {
return {
scope: {
addName: '='
},
template: '<div><my-dir-3 add-name="addName"></my-dir-3></div>'
}
})
.directive('myDir3', function() {
return {
scope: {
addName: '='
},
template: "<span ng-click='addName(testName)'>Click to Add {{testName}}!</span>",
controller: function($scope) {
$scope.testName = "Henry"
}
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With