【问题标题】:Angular - Passing an object from one directive to another directiveAngular - 将对象从一个指令传递到另一个指令
【发布时间】:2016-12-22 14:10:19
【问题描述】:

我是 Angular 的新手,所以如果问题太新手,请提前道歉。我正在尝试制作一个自定义指令,并且由于我已经在使用angular-youtube-embed 指令,因此在我的新指令中,我需要将player 对象从youtube-video 指令传递给我的新指令,用于函数@ 987654325@ 在我的范围内使用它。我想知道该怎么做? 这是我的指令的外观:

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

其中player 是我从angular-youtube-embed package 使用的youtube-video 指令的对象。 因此,每当用户点击下面的元素时,$scope.videoPlaying 应变为 trueplayVideo() 函数应开始播放视频:

<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” 未定义:

【问题讨论】:

  • 我在回答中包含了一个 JSFiddle,其中包含您所描述的工作 youtube-player 指令。

标签: javascript angularjs


【解决方案1】:

像这样更改前缀 @videoPlaying 到 =videoPlaying 和 @playVideo 到 &playVideo

变量前的@@ 被角度评估为字符串值,在这种情况下您需要使用双向绑定。

【讨论】:

  • 我已经按照你的建议做了,这次我得到了正确的类,所以覆盖没有隐藏,但是当我点击覆盖时,什么也没发生,它没有像应该的那样被隐藏是。
  • 发送到函数playVideo的播放器对象,从哪里来?在您提供的代码中看不到这一点。也许这就是问题?日志有没有给你任何线索?
  • 播放器是另一个指令的对象,我使用的是 angular-youtube-embed。我在日志中看不到任何内容,没有错误或任何可以给我一些指示的内容。
  • 你能把代码放在一个 plunkr 中以便更容易获得一些概览吗?
  • 我已经把我项目中的所有相关文件都放入了这个plunkerplnkr.co/edit/VplG64o99Dnh5ycmP7tY?p=preview,希望能有所帮助
【解决方案2】:

您可以使用 '&' 类型在指令中传递函数:

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()"使用 '=' 类型参数(但这样,您的函数将被双重绑定。在大多数情况下,您不必担心函数)。

您可以在 this 旧帖子中找到一些示例:尝试任一解决方案,然后找到适合您的情况的解决方案。

【讨论】:

  • 我已经尝试过您的建议,但没有任何改变。
  • 您也可以使用 '=' 以 action="playVideo()" 的形式将函数传递给您的指令(它们将以相同的方式执行)
  • @Luxor001 我很确定action: '&amp;' 会导致关闭,所以如果你在使用指令时设置action="playVideo(someVideo)",指令本身可能会调用$scope.action({someVideo: myVideo}),它会导致播放someVideo。如果您改用'=',Angular 将在摘要期间评估playVideo(someVideo),并将$scope.action 设置为playVideo 的返回值,这可能在这种情况下不是您想要的。
  • 实际上,如果您有嵌套指令,这似乎是可行的方法(但我必须承认,它的工作方式(以及为什么工作)是晦涩难懂的)。看到这篇文章:codepen.io/justinbc820/post/…
【解决方案3】:

查看指令中的按钮:

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

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

因此,当您在 player 对象上调用函数 .playVideo() 时,您实际上是在尝试在函数调用中创建的对象上调用它:{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,显示了您的指令应该如何工作的一般概念。

【讨论】:

    【解决方案4】:

    首先,你的问题是矛盾的。在您的 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'是不可分配的!
    • 能否提供 Plunkr 解决此问题?
    【解决方案5】:

    最简单的方法是在指令中使用 $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);
                });
            }
        }
    });
    

    控制器你将在这里捕获你在 action(directive) 中定义的函数。

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

    【讨论】:

      【解决方案6】:

      您可以为此创建一个角度服务并在项目的任何地方使用它。该服务包含您在多个指令中需要的所有类型的功能。

      【讨论】:

        【解决方案7】:

        您可以使用 $broadcast 来实现这一点。

        下面是解释这个概念的图表。

        在 youtubePlayer 指令中使用广播 -

        $rootscope.$broadcast('player-object', $scope.player);
        

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

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

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

        【讨论】:

          【解决方案8】:

          将对象传递给 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 接受一个参数,该参数允许指令覆盖表达式中局部变量的值。如果没有提供覆盖,它将查找具有该名称的父范围的属性,如果不存在这样的属性,它将 undefined 传递给函数。所以在这种情况下:

          $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 元素。

          我希望这有助于解决问题。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-09-28
            • 1970-01-01
            • 2014-09-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-06-05
            相关资源
            最近更新 更多