The best way to pass an object to an angular directive is by using the &.
From the Angular Docs:
The & binding allows a directive to trigger evaluation of an
expression in the context of the original scope, at a specific time.
Any legal expression is allowed, including an expression which
contains a function call
When you use &, angular compiles the string as an expression and sets the scope variable in your directive to a function that, when called, will evaluate the expression in the context of the directive's parent's scope.
I'm going to make a small change to your directive to help clarify my explanation.
angular.module('coop.directives')
.directive('youtubePlayer', function () {
return {
restrict: 'E',
scope: {
videoPlaying: '=videoPlaying',
foo: '&playVideo',
playerVars: '=playerVars',
article: '=article'
},
templateUrl : 'templates/youtube-player.html'
};
});
I changed the name of the directive scope variable from playVideo to foo. From here forward, playVideo is a property of the parent, while foo is the property bound by the & binding to a property of the directive. Hopefully the different names will make things more clear (they are, in fact, completely separate properties/methods.
In your case, the object you are trying to pass is a function. In this case, there are two options, both are subtly different and depend on how you want the consumer of the directive to use it.
Consider this usage:
<youtube-player video-playing="videoPlaying" foo="playVideo()" player-vars="playerVars" article="article"></youtube-player>
In this case, the expression is "playVideo()". The & directive will create a property in your directive scope called "foo" that is a function that, when called, evaluates that expression in the parent scope. In this case, evaluating this expression would result in the parent scope's playVideo method being invoked with no arguments.
In this usage, your directive can only call the parent scope's method as is. No parameters can be overridden or passed to the function.
So:
foo() -> parent.playVideo()
foo(123) -> parent.playVideo() argument ignored
foo({player: 'xyz'}) -> parent.playVideo() argument ignored
Probably the preferred method if your parent method (playVideo) does not take any arguments.
Now consider a small change to the expression:
<youtube-player video-playing="videoPlaying" foo="playVideo(player)" player-vars="playerVars" article="article"></youtube-player>
Note the introduction of the local variable "player" in the expression. The function that is created in the directive's scope will do exactly the same thing as in the previous example, but it can now be called in two different ways. The variable "player" is considered a local variable in the expression.
The function foo generated by angular takes an argument that allows the directive to override the value of local variables in an expression. If no override is provided, it looks for a property of the parent scope with that name, if no such property exists, it will pass undefined to the function. So in this case:
$scope.foo() -> parent.playVideo(parent.player)
$scope.foo(123) -> parent.playVideo(parent.player)
$scope.foo({player: 'xyz'}) -> parent.playVideo('xyz')
If you want to pass the player from the directive to the parent, this is a weird way to do it (IMHO), because you have to know the name of the local variable in the expression. That creates an unnecessary requirement that the directive and the expression agree on the name of the argument.
The final way the playVideo function could be bound is:
<youtube-player video-playing="videoPlaying" foo="playVideo" player-vars="playerVars" article="article"></youtube-player>
In this case, the expression, evaluated against the parent, returns the function playVideo of the parent. In the directive, to call the function, you then have to invoke it.
$scope.foo() -> noop (you now have a pointer to the parent.playVideo function
$scope.foo()() -> parent.playVideo()
$scope.foo()('xyz') -> parent.playVideo('xyz')
This last way, in my very humble opinion, is the proper way to pass a function pointer that takes an argument to a directive and use it within the directive.
There are some esoteric side effects that can be used (but shouldn't). For instance
$scope.foo({playVideo: function(){
alert('what????')
})();
This will not call the parent.playVideo function since you've overriden the expression's local variable "playVideo" with a custom version in the directive. Instead, it will pop up an alert dialog. Weird, but that's the way it works.
So, why not use @ or =?
If you use @, you essentially have to do what & does manually in the directive. Why do that when & will do it for you? '=' actually sets up two way binding, allowing the directive to change the value of the parent's property (potentially changing the function itself!) and vice-versa. Not a desirable side effect. This two-way binding also requires two watches which essentially are doing nothing but taking up cpu cycles since you aren't likely using them to update UI elements.
I hope this helps clear things up.