Angular - Передача объекта из одной директивы в другую директиву

Я новичок в angular, поэтому извиняюсь перед тем, если вопрос слишком новичок. Я пытаюсь создать пользовательскую директиву, и поскольку я уже использую директиву angular-youtube-embed, в моей новой директиве мне нужно передать объект player из youtube-video, в мою новую директиву, для функции 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();
  };

Где player является объектом директивы youtube-video, которую я использую из angular -youtube-embed package. Таким образом, всякий раз, когда пользователь нажимает на элемент ниже, $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' undefined:

Ответы

Ответ 1

Вы можете использовать '&' тип для передачи функции в директивах:

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()" или использовать '=' тип параметра (но таким образом, ваша функция будет дважды привязана. В большинстве случаев вам не придется беспокоиться об этом для функций, так или иначе).

Вы можете найти несколько примеров в этом старом посту: попробуйте либо решить, либо найти, какой из них работает для вашего дела.

Ответ 2

Вы можете использовать $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/

Ответ 3

Измените префиксы следующим образом @videoPlaying to = videoПланирование и @playVideo to & playVideo

Параметры @перед переменными оцениваются как строковые значения angular, и в этом случае вам необходимо использовать двустороннюю привязку.

Ответ 4

Посмотрите на свою кнопку в своей директиве:

<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, показывающий общую концепцию работы вашей директивы.

Ответ 5

Прежде всего, ваш вопрос противоречит. В вашем 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 на самом деле равна undefined, и поэтому директива youtube-video пытается присвоить значения недоступному объекту. Чтобы решить эту проблему, назначьте пустой объект в player в директивном контроллере youtube-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 = {};
        }
    };
}); 

Ответ 6

проще всего использовать директиву $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);

Ответ 7

Вы можете создать для этого службу angular и использовать ее в любом месте проекта. Эта служба содержит все типы функций, которые вам нужны в нескольких директивах.

Ответ 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". Функция, созданная в области директивы, будет делать то же самое, что и в предыдущем примере, но теперь она может быть вызвана двумя разными способами. Переменная "игрок" считается локальной переменной в выражении.

Функция foo, сгенерированная с помощью angular, принимает аргумент, который позволяет директиве переопределять значение локальных переменных в выражении. Если переопределение не предусмотрено, оно ищет свойство родительской области с этим именем, если такое свойство не существует, оно передаст функции undefined. Итак, в этом случае:

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

Если вы хотите передать игрока из директивы родительскому, это странный способ сделать это (IMHO), потому что вам нужно знать имя локальной переменной в выражении. Это создает ненужное требование о том, что директива и выражение согласуются с именем аргумента.

Последний способ, которым может быть привязана функция 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" с пользовательской версией в директиве. Вместо этого появится диалоговое окно предупреждения. Странно, но так, как это работает.

Итак, почему бы не использовать @или =?

Если вы используете @, вам, по сути, нужно что-то делать и делать вручную в директиве. Зачем это делать и делать это за вас? '=' фактически устанавливает двустороннюю привязку, позволяя директиве изменять значение родительского свойства (потенциально меняя саму функцию!) и наоборот. Нежелательный побочный эффект. Это двухстороннее связывание также требует двух часов, которые по существу ничего не делают, кроме взятия циклов процессора, поскольку вы вряд ли используете их для обновления элементов интерфейса.

Я надеюсь, что это поможет разобраться.