Reputation: 1811
I am new to angular so apologies up front if a question is too newbie. I am trying to make a custom directive, and since I am already using an angular-youtube-embed directive, inside my new directive, I need to pass a player
object from youtube-video
directive, to my new directive, for the function playVideo
in my scope to use it. I wonder how to do that?
This is how my directive looks:
angular.module('coop.directives')
.directive('youtubePlayer', function () {
return {
restrict: 'E',
scope: {
videoPlaying: '=videoPlaying',
playVideo: '&playVideo',
playerVars: '=playerVars',
article: '=article'
},
templateUrl : 'templates/youtube-player.html'
};
});
This is my youtube-player.html:
<img ng-hide='videoPlaying' ng-src='http://i1.ytimg.com/vi/{{ article.external_media[0].video_id }}/maxresdefault.jpg' class='cover'>
<youtube-video ng-if='videoPlaying' video-url='article.external_media[0].original_url' player='player' player-vars='playerVars' class='video'></youtube-video>
<div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo({player: player})'>
<img ng-hide='videoPlaying' class='play' src='icons/play.svg'/>
<img ng-hide='videoPlaying' class='playButton' src='icons/playRectangle.svg'/>
</div>
And this is the function from the controller that I would like to use in my directive:
$scope.playVideo = function(player) {
$scope.videoPlaying = true;
player.playVideo();
};
Where player
is an object of youtube-video
directive that I am using from angular-youtube-embed package.
So, whenever a user clicks on an element below, $scope.videoPlaying
should become true
and a playVideo()
function should start the video:
<div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo(player)'>
This is how I call my directive in the view:
<youtube-player video-playing="videoPlaying" play-video="playVideo()" player-vars="playerVars" article="article"></youtube-player>
I should somehow pass a player object from youtube video to my new directive because now I get an error of:
ionic.bundle.js:26794 TypeError: Cannot read property 'playVideo' of undefined:
Upvotes: 16
Views: 6749
Reputation: 2947
You can use '&' type for passing function in directives:
angular.module('coop.directives')
.directive('youtubePlayer', function () {
return {
restrict: 'E',
scope: {
action: '&', //<- this type of parameter lets pass function to directives
videoPlaying: '@videoPlaying',
...
so you directive will accept a parameter as a function, like this:
<coop.directives action="playVideo" videoPlaying="video" ...> </coop.directives>
and you'll be able to call that function normally:
article: '=article'
},
template : "<img ng-hide='videoPlaying' ng-src='http://i1.ytimg.com/vi/{{ article.external_media[0].video_id }}/maxresdefault.jpg' class='cover'><youtube-video ng-if='videoPlaying' video-url='article.external_media[0].original_url' player='player' player-vars='playerVars' class='video'></youtube-video><div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo(player)'><img ng-hide='videoPlaying' class='play' src='icons/play.svg'/><img ng-hide='videoPlaying' class='playButton' src='icons/playRectangle.svg'/></div>",
link: function (scope, element) {
scope.action();
}
If none of those suggestions works, you can try to add () brackets to you action parameter action="playVideo()"
or use '=' type parameter (but this way, your function will be double binded. In most cases you don't have to worry about it for functions, anyway).
You can find some examples in this old post: just try either solutions and find which one is working for your case.
Upvotes: 5
Reputation: 11190
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.
Upvotes: 0
Reputation: 647
Look at your button in your directive:
<div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo({player: player})'>
You are not passing player
to the function, you are actually passing player
as the value of a property on an object that you are creating within the function call: {player: player}
So when you go to call the function .playVideo()
on the player
object, you are actually trying to call it on the object you created in the function call: {player: player}
which obviously doesn't have a function in it.
To fix it, you need to either change your function, or change the player object being passing into the function. Instead of this:
$scope.playVideo = function(player) {
$scope.videoPlaying = true;
player.playVideo();
};
You would need to change it to this:
$scope.playVideo = function(player) {
$scope.videoPlaying = true;
player.player.playVideo();
};
Or, alternatively, leave the function alone and change the object you are passing in:
<div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo(player)'>
JSFiddle
I've also created a JSFiddle showing the general concept of how your directive should be working.
Upvotes: 2
Reputation: 528
You can use $broadcast to achieve this.
Below is the diagram explaining the concept.
In youtubePlayer Directive use broadcast -
$rootscope.$broadcast('player-object', $scope.player);
And receive it in your custom directive.
$scope.$on('player-object', function (event, player) {
$scope.videoPlaying = true;
player.playVideo();
});
Sample Example -http://jsfiddle.net/HB7LU/10364/
Upvotes: 6
Reputation: 767
You can create an angular service for that and use it anywhere in the project. This service contains all type of functionality that you need in multiple directives.
Upvotes: 0
Reputation: 1745
easiest will be use $rootScope in directive and assign player in rootscope then use it in controller.
or better approach will be using directive.
directive: in action you will assign a function with parameter.
rootApp.directive('ListTemplate', function () {
return {
restrict: 'EA',
replace: true,
transclude: true,
scope: {
list: '=',
action: '='
},
template: ' <div ng-click="bindSelectedGuest(guest.guid)" class="ct-clearfix info" ng-repeat="guest in list track by $index" data-tag="{{activeUser.guestId}}" ng-class="{ active : guest.guid==activeUser.guestId}">' +
'<label class="col-md-6 col-lg-7 ct-pull-left" data-tag="{{action}}" title="{{guest.firstName}}">{{guest.firstName}}</label>' +
'<label class="col-md-6 col-lg-5 ct-pull-right"><span class="fr" ng-if="guest.mobile" title="{{guest.displayMobile}}">{{guest.displayMobile}}</span>' +
'<span class="fr" ng-if="!guest.mobile">{{"N/A"}}</span>' +
'</label>' +
'<div class="info" ng-show="list.length==0"><div class="detail_alert message">No Record found</div></div></div>',
link: function ($scope, e, a) {
$scope.$watch('list', function () {
//console.log(list);
});
}
}
});
controller: you will capture function you defined in action(directive) here.
> $scope.bindSelectedGuest($scope.selectedGuest.guid);
Upvotes: 0
Reputation: 2173
First of all, your question is contradicting. In your youtube-player.html, you use playVideo({player: player})
<div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo({player: player})'>
and just below that you say you use it as playVideo(player)
.
<div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo(player)'>
Assuming it is the second version, the problem here might be that the player
reference actually is undefined and hence the youtube-video directive tries to assign values to an object that is not available. In order to solve this, assign an empty object to player
in your youtube-player directive's controller.
angular.module('coop.directives').directive('youtubePlayer', function () {
return {
restrict: 'E',
scope: {
videoPlaying: '=videoPlaying',
playVideo: '&playVideo',
playerVars: '=playerVars',
article: '=article'
},
templateUrl : 'templates/youtube-player.html',
controller: function($scope) {
$scope.player = {};
}
};
});
Upvotes: 2
Reputation: 451
Change the prefixes like this @videoPlaying to =videoPlaying and @playVideo to &playVideo
The @ before variables is evaluated as string values by angular and you need to use two-way-binding in this case.
Upvotes: 2