Angular - 将一个对象从一个指令传递到另一个指令

16

我刚接触Angular,如果问题太新手了,请提前谅解。我正在尝试创建一个自定义指令,并且由于我已经在使用angular-youtube-embed指令,所以在我的新指令中,我需要将youtube-video指令中的player对象传递给我的新指令,以便于我的scope中的playVideo函数能够使用它。请问我该如何实现?以下是我的指令的代码:

angular.module('coop.directives')
.directive('youtubePlayer', function () {
    return {
        restrict: 'E',
        scope: {
          videoPlaying: '=videoPlaying',
          playVideo: '&playVideo',
          playerVars: '=playerVars',
          article: '=article'
         },
        templateUrl : 'templates/youtube-player.html'
    };
}); 

这是我的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>

这是我想在指令中使用的来自控制器的函数:

  $scope.playVideo = function(player) {
    $scope.videoPlaying = true;
    player.playVideo();
  };

在使用angular-youtube-embed包中的youtube-video指令对象中,player是一个对象。因此,每当用户点击下面的元素时,$scope.videoPlaying应该变为true,并且一个playVideo()函数应该启动视频:

<div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo(player)'>

这是我在视图中调用指令的方式:

<youtube-player video-playing="videoPlaying" play-video="playVideo()" player-vars="playerVars" article="article"></youtube-player>

我需要以某种方式将Youtube视频中的播放器对象传递到我的自定义指令中,因为现在我遇到了如下错误:

ionic.bundle.js:26794 TypeError: 无法读取未定义的 'playVideo' 属性:


我在回答中包含了一个包含工作的 Youtube 播放器指令的 JSFiddle,就像您所描述的那样。 - I think I can code
8个回答

6
你可以使用$broadcast来实现这一点。
下面是解释该概念的图表。

enter image description here

在youtubePlayer指令中使用广播 -
$rootscope.$broadcast('player-object', $scope.player);

并在您的自定义指令中接收它。

$scope.$on('player-object', function (event, player) {
    $scope.videoPlaying = true;
    player.playVideo();
 });

示例 -http://jsfiddle.net/HB7LU/10364/


5
您可以在指令中使用“&”类型来传递函数:
angular.module('coop.directives')
  .directive('youtubePlayer', function () {
    return {
        restrict: 'E',
        scope: {
          action: '&', //<- this type of parameter lets pass function to directives
          videoPlaying: '@videoPlaying',
          ...

因此,您的指令将接受一个函数作为参数,就像这样:

<coop.directives action="playVideo" videoPlaying="video" ...> </coop.directives>

然后您就可以正常调用该函数:

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

编辑1:

如果以上建议都不起作用,您可以尝试在操作参数action="playVideo()"中添加()括号或使用“='”类型参数(但这种方式会使您的函数双向绑定,在大多数情况下您不必为函数担心它)。

您可以在旧帖子中找到一些示例:只需尝试其中一个解决方案,找出哪个适用于您的情况即可。


我尝试了你的建议,但是没有任何改变。 - Ludwig
你也可以使用“=”将函数传递给指令,形式为action="playVideo()"(它们将以相同的方式执行)。 - illeb
@Luxor001 我非常确定action: '&'会导致闭包。因此,如果在使用指令时设置action="playVideo(someVideo)",则该指令本身可能会调用$scope.action({someVideo: myVideo})并导致播放someVideo。如果使用 '=',则Angular将在digest期间评估playVideo(someVideo)并将$scope.action设置为playVideo的返回值,这在这种情况下很可能不是您想要的。 - Jesse Amano
实际上,如果您有嵌套指令,那似乎是正确的方法(但我必须承认,它的工作方式(以及为什么它起作用)很难理解)。请参阅此文章:http://codepen.io/justinbc820/post/calling-controller-function-within-nested-angular-directive - illeb

2

更改前缀,像这样 @videoPlaying 改为 =videoPlaying,并且 @playVideo 改为 &playVideo

在angular中,变量前的@符号会被解释为字符串值,因此在这种情况下需要使用双向绑定。


我按照你的建议做了,这次我正确地获取了类,因此覆盖层不再隐藏,但是当我点击覆盖层时,什么也没有发生,它没有像应该隐藏一样。 - Ludwig
传递到函数playVideo中的player对象从哪里来?我在您提供的代码中看不到它。也许这就是问题所在?日志是否给您任何线索? - hesa
在plunkr中,指令看起来像是<youtube-player video-playing="false" action="playVideo" play-video="onVideoPlay" article="article"></youtube-player>,但我找不到函数onVideoPlay。尝试将其更改为playVideo并删除未在指令中使用的属性action。 - hesa
你的意思是要更改为这样:<youtube-player video-playing="false" play-video="playVideo" article="article"></youtube-player>吗?我已经这样做了,但点击后仍然没有任何反应。 - Ludwig
我认为问题出在播放器对象上。你说它是来自另一个指令的对象,那么你的指令youtubePlayer就无法访问它。尝试在playVideo函数中加入console.log并记录player对象。但除此之外,我没有其他建议了。抱歉。 - hesa
显示剩余4条评论

2

请看您指令中的按钮:

<div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo({player: player})'>

您没有将player传递给函数,实际上您正在将player作为在函数调用中创建的对象属性的值进行传递:{player: player}

因此,当您调用.playVideo()函数时,在player对象上调用它时,实际上是在函数调用中创建的对象上调用它:{player: player},而该对象显然不具有该函数。

要修复它,您需要更改函数或更改传入函数的播放器对象。而不是这样:

$scope.playVideo = function(player) {
  $scope.videoPlaying = true;
  player.playVideo();
};

您需要将其更改为以下内容:
$scope.playVideo = function(player) {
  $scope.videoPlaying = true;
  player.player.playVideo();
};

或者,作为另一种选择,保持函数不变,改变你传入的对象:
<div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo(player)'>

JSFiddle

我还创建了一个JSFiddle,展示了指令应该如何工作的一般概念。


2
首先,您的问题有矛盾之处。在您的youtube-player.html文件中,您使用了playVideo({player: player})
<div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo({player: player})'>

而你刚才说你使用playVideo(player)

<div ng-hide='videoPlaying' class='iframe-overlay' ng-click='playVideo(player)'>

假设这是第二个版本,这里的问题可能是 player 引用实际上是未定义的,因此 youtube-video 指令尝试为不可用的对象分配值。为了解决这个问题,在您的 youtube-player 指令控制器中将一个空对象分配给 player

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

我尝试了你的建议,但是我得到了以下错误信息:错误:[$compile:nonassign]在指令“youtubePlayer”中使用属性“player”时,表达式“undefined”是不可分配的! - Ludwig
你能提供一个包含这个问题的 Plunkr 吗? - Lizzy

0
你可以创建一个 Angular 服务,并在项目中任何地方使用它。这个服务包含了你在多个指令中需要的所有类型的功能。

0
最简单的方法是在指令中使用$rootScope,并将播放器分配给rootscope,然后在控制器中使用它。
或者更好的方法是使用指令。
指令: 在操作中,您将分配一个带参数的函数。
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);
            });
        }
    }
});

控制器在这里,您将捕获您在操作(指令)中定义的函数。

>  $scope.bindSelectedGuest($scope.selectedGuest.guid);

0

将对象传递给Angular指令的最佳方法是使用 &。

来自Angular文档:

&绑定允许指令在特定时间触发在原始作用域中评估表达式。任何合法的表达式都是允许的,包括包含函数调用的表达式

当您使用&时,Angular会将字符串编译为表达式,并将指令中的作用域变量设置为一个函数,该函数在调用时将在指令父级作用域的上下文中评估表达式。

我将对您的指令进行小修改,以帮助澄清我的解释。

angular.module('coop.directives')
.directive('youtubePlayer', function () {
    return {
        restrict: 'E',
        scope: {
          videoPlaying: '=videoPlaying',
          foo: '&playVideo',
          playerVars: '=playerVars',
          article: '=article'
         },
        templateUrl : 'templates/youtube-player.html'
    };
}); 

我将指令作用域变量的名称从playVideo更改为foo。从这里开始,playVideo是父级属性,而foo是由&绑定到指令属性的属性。希望不同的名称可以使事情更清晰(它们实际上是完全独立的属性/方法)。

在您的情况下,您要传递的对象是一个函数。在这种情况下,有两个选项,两者都略有不同,取决于您希望指令的使用者如何使用它。

考虑以下用法:

<youtube-player video-playing="videoPlaying" foo="playVideo()" player-vars="playerVars" article="article"></youtube-player>

在这种情况下,表达式是“playVideo()”。 &指令将在您的指令作用域中创建一个名为“foo”的属性,该属性是一个函数,当调用时,会在父作用域中评估该表达式。 在这种情况下,评估此表达式将导致调用父作用域的playVideo方法而不带任何参数。
在此用法中,您的指令只能按原样调用父作用域的方法。 无法覆盖或传递参数给该函数。
所以:
foo() -> parent.playVideo() 
foo(123) -> parent.playVideo() argument ignored
foo({player: 'xyz'}) -> parent.playVideo() argument ignored

如果您的父方法(playVideo)不带任何参数,则可能是首选方法。

现在考虑对表达式进行小改动:

<youtube-player video-playing="videoPlaying" foo="playVideo(player)" player-vars="playerVars" article="article"></youtube-player>

请注意在表达式中引入了本地变量“player”。指令作用域中创建的函数将与前面的示例完全相同,但现在可以以两种不同的方式调用它。变量“player”被视为表达式中的本地变量。

由Angular生成的函数foo接受一个参数,允许指令覆盖表达式中的本地变量的值。如果没有提供覆盖,则它会查找具有该名称的父级作用域属性,如果不存在这样的属性,则会将未定义传递给函数。因此,在这种情况下:

$scope.foo() -> parent.playVideo(parent.player) 
$scope.foo(123) -> parent.playVideo(parent.player) 
$scope.foo({player: 'xyz'}) -> parent.playVideo('xyz')

如果你想从指令传递播放器到父级,这是一种奇怪的方式(在我看来),因为你必须知道表达式中本地变量的名称。这会创建一个不必要的要求,即指令和表达式必须就参数的名称达成一致。

绑定playVideo函数的最终方式可能是:

<youtube-player video-playing="videoPlaying" foo="playVideo" player-vars="playerVars" article="article"></youtube-player>

在这种情况下,对父级进行评估的表达式将返回父级的playVideo函数。在指令中,要调用该函数,您需要显式地调用它。
$scope.foo() -> noop (you now have a pointer to the parent.playVideo function
$scope.foo()() ->  parent.playVideo()
$scope.foo()('xyz') -> parent.playVideo('xyz')

在我非常谦逊的意见中,最后一种方式是将带有参数的函数指针传递到指令并在指令中使用的正确方式。
还有一些奇怪的副作用可以使用(但不应该)。例如:
$scope.foo({playVideo: function(){
    alert('what????')
})();  

这不会调用parent.playVideo函数,因为你在指令中覆盖了表达式的局部变量"playVideo"。相反,它会弹出一个警告对话框。有点奇怪,但这就是它的工作方式。

那么,为什么不使用@或=呢?

如果你使用@,实际上你必须在指令中手动执行&的操作。为什么要这样做,当&可以为你完成呢?'='实际上设置了双向绑定,允许指令改变父级属性的值(可能改变函数本身!)反之亦然。这种双向绑定还需要两个监视器,实际上只是占用CPU周期,因为你可能不会使用它们来更新UI元素。

希望这能帮助澄清问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接