Angular.js: angular 1.2.18: проблема ng-repeat с включением

Созданный на 17 июн. 2014  ·  48Комментарии  ·  Источник: angular/angular.js

При переходе к директиве с помощью ng-transclude html-контент со ссылкой {{item}}, который вы хотите повторить (через ng-repeat = "item in collection", реализованный в директиве), не работает с версией 1.2.18.

http://embed.plnkr.co/EvzF25sPD3uZLQivDFqy/preview

Самый полезный комментарий

В итоге я просто использовал $parent . Это кладовая ванили без необходимости добавлять слишком много вещей.

Так что у меня что-то вроде:

angular.module('test').directive('myDirectiveWithTransclusion', function() {
     return {
          restrict: 'E'
          transclude: {
               transcludeThis: 'transcludeThis'
          }
          template: "<div ng-repeat='item in array'><div ng-transclude='transcludeThis'></div></div>"
     }
})
<my-directive-with-transclusion>
     <transclude-this>
          {{$parent.item}}
     </transclude-this>
</my-directive-with-transclusion>

Все 48 Комментарий

о, лоуди. @petebacondarwin хотите сделать еще один из них? это на самом деле вещь "не создавайте одноуровневую область видимости с ng-transclude" снова, просто она работала раньше для этого случая из-за неисправности

(возможно) связанные: https://github.com/angular/angular.js/issues/7842

К сожалению, включение ng-transclude работает не так. Что вы пытаетесь сделать, так это создать директиву контейнера, которая использует своих дочерних элементов в качестве «шаблона» того, что нужно исключить внутри вашей собственной директивы.

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

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

Так что на самом деле вам действительно не нужно использовать здесь включение, поскольку вы действительно пытаетесь просто вставить внутренний HTML в свой собственный шаблон. Вы можете сделать это в функции компиляции следующим образом:

http://plnkr.co/edit/j3NwMGxkVRM6QMhmydQC?p=preview

app.directive('test', function(){

  return {
    restrict: 'E',
    compile: function compile(tElement, tAttrs, tTransclude) {

      // Extract the children from this instance of the directive
      var children = tElement.children();

      // Wrap the chidren in our template
      var template = angular.element('<div ng-repeat="item in collection"></div>');
      template.append(children);

      // Append this new template to our compile element
      tElement.html('');
      tElement.append(template);

      return {
        pre: function preLink(scope, iElement, iAttrs, crtl, transclude) {
            scope.collection = [1, 2, 3, 4, 5];
        },
        post: function postLink(scope, iElement, iAttrs, controller) {
          console.log(iElement[0]);
        }
      };
    }
  };
});

Другой пример этого (я считаю):

Index.html:

<html ng-app='myApp'>

<head>
    <title>AngularJS Scopes</title>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js"></script>
    <script src='index.js'></script>
</head>

<body ng-controller='myController'>
    <people>Hello {{person.name}}</people>
</body>
</html>

index.js:

var myApp = angular.module( 'myApp', [] );

myApp.controller( 'myController', function( $scope ) {
    $scope.people = [
        { name: 'Rob'  },
        { name: 'Alex' },
        { name: 'John' }
    ];
});

myApp.directive( 'people', function() {
    return {
        restrict: 'E',

        transclude: true,
        template: '<div ng-repeat="person in people" ng-transclude></div>',
    }
});

Работал с Angular 1.2.1, но не с 1.2.18;

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

... иногда желательно иметь возможность передать весь шаблон, а не строку или объект. Допустим, мы хотим создать компонент «диалоговое окно». Диалоговое окно должно содержать любой произвольный контент.

В то время как документация ngTransclude говорит:

Директива, которая отмечает точку вставки для включенной DOM ближайшей родительской директивы, которая использует включение. Любое существующее содержимое элемента, на котором размещена эта директива, будет удалено перед вставкой включенного содержимого.

Чем это отличается от определения @petebacondarwin :

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

Я действительно не понимаю, почему включение здесь не подходящее решение. Во всяком случае, я бы ожидал, что разумное решение будет включать в себя введение области шаблона в функцию включения.

Разница в том, что включенный контент привязан к "внешнему", то есть к области, в которой находится элемент <people> .
В то время как вы хотите, чтобы встроенный шаблон был привязан к «внутреннему», то есть к области действия директивы.
Если ваша директива не создает свою собственную область видимости, то это примерно то же самое. Если ваша директива создает, скажем, изолированную область видимости, то это определенно не одно и то же. Внутренняя область не имеет доступа к внешней области.

Я думаю, вы могли бы создать свою собственную директиву, которая действительно вводит область действия шаблона в функцию включения ...

То, что вы говорите, имеет смысл, но это только в том случае, если вы понимаете всю глубину Angular. Я считаю, что приведенный выше пример должен каким-то образом работать без излишней дополнительной работы.

Мне кажется очень разумным и очень практичным для функции включения возможность каким-либо образом получить доступ к области шаблона (или «внутренней») области. Я могу вспомнить множество случаев, зачем это понадобится.

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

И большое спасибо за код. Я воспроизвожу это здесь для других:

var myApp = angular.module( 'myApp', [] );

myApp.controller( 'myController', function( $scope ) {
    $scope.people = [
        { name: 'Rob'  },
        { name: 'Alex' },
        { name: 'John' }
    ];
});

myApp.directive( 'people', function() {
    return {
        restrict: 'E',
        transclude: true,
        template: '<div ng-repeat="person in people" inject></div>'
    }
});

myApp.directive('inject', function(){
  return {
    link: function($scope, $element, $attrs, controller, $transclude) {
      if (!$transclude) {
        throw minErr('ngTransclude')('orphan',
         'Illegal use of ngTransclude directive in the template! ' +
         'No parent directive that requires a transclusion found. ' +
         'Element: {0}',
         startingTag($element));
      }
      var innerScope = $scope.$new();
      $transclude(innerScope, function(clone) {
        $element.empty();
        $element.append(clone);
        $element.on('$destroy', function() {
          innerScope.$destroy();
        });
      });
    }
  };
});

:-) Я согласен с тем, что переключение - непростая тема для понимания без множества царапин в голове и стука по клавиатуре.
Возможно, нам нужно еще больше уточнить документацию относительно того факта, что ng-transclude будет связывать включенный контент с «внешней» областью?

Лично я считаю, что текущая документация, по крайней мере, на этой ключевой странице , довольно ясна и ясна:

Что именно делает этот параметр включения? transclude позволяет содержимому директивы с этой опцией иметь доступ к области за пределами директивы, а не внутри.

(и все последующее иллюстрирует это далее).

Я бы подумал о том, чтобы добавить, возможно, директиву, которую вы предоставили в структуру, возможно, обозначив ее как «ng-transclude-internal»? Я знаю по крайней мере еще одного человека, который попробовал это сделать с помощью директивы под названием «transcope».

Я пробовал решение, но столкнулся с другой проблемой: почему в родительскую область ng-repeat не входит область действия директивы

http://plnkr.co/edit/7j92IC?p=preview

@luboid причина, которая не работает в вашем плункере, заключается в том, что ваша директива имеет изолированную область видимости, а модифицированный скомпилированный DOM (кстати, не делайте этого, это глупый способ решить эту проблему) будет использовать брат родителя изолированной области видимости.

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

На самом деле, если подумать, с помощью ng-repeat или других директив включения элементов вы не можете это исправить. Так что да, это не будет работать примерно с версии 1.2.0.

@petebacondarwin Здесь действительно вопрос для новичков. Зачем использовать var innerScope вместо просто:

myApp.directive( 'inject', function() {
    return {
        link: function( $scope, $element, $attrs, controller, $transclude ) {
            if ( !$transclude ) {
                throw minErr( 'ngTransclude' )( 'orphan',
                    'Illegal use of ngTransclude directive in the template! ' +
                    'No parent directive that requires a transclusion found. ' +
                    'Element: {0}',
                    startingTag( $element ));
            }

            $transclude( $scope, function( clone ) {
                $element.empty();
                $element.append( clone );
            });
        }
    };
}); 

Как правило, лучше создать новую область видимости, если вы собираетесь компилировать некоторые новые элементы, поскольку вы не уверены, совмещена ли исходная директива с какой-то сложной директивой, которая также хочет создать область и т. Д. Но вы можете получить прочь сюда ...

Спасибо за помощь,
Я буду использовать внешние шаблоны ($ templateCache), тогда все будет немного проще, базовый шаблон компилируется с областью действия директивы.

@Izhaki @petebacondarwin Большое спасибо за директиву include. Только что наконец-то обновился с 1.2.16 до 1.2.20, и потребовалось немного времени, чтобы понять, почему мое приложение начало так сильно ломаться.

Я думал, что включение - это довольно простая концепция, прежде чем я нашел эту ветку. Неа.

Спасибо всем, кто помогал прояснить это. Обновление до 1.2.21 сломало большую часть нашего интерфейса из-за проблем, описанных здесь, и теперь мы на правильном пути.

@caitp @petebacondarwin : Было бы очень полезно, если бы мы могли получить две дополнительные части информации:

  1. Какое критическое изменение, которое произошло где-то «около версии 1.2.0» (как сказала Кейтлин), привело к тому, что этот подход внезапно взорвался? Я понимаю, что больше не работает, но я не вижу в журнале изменений рядом с этим выпуском какой-либо конкретной вещи, которая, кажется, коррелирует с этой конкретной проблемой.
  2. Какой альтернативный подход конечным пользователям следует использовать вместо ng-transclude, если нам нужны изолированные многоразовые контейнеры, от которых может наследоваться произвольное внутреннее содержимое? Пример: большое мультитенантное приложение, в котором даже интерфейс является переменным и полностью управляется данными. Итак, у нас есть такие вещи, как элементы управления списком, которые должны извлекать данные для заполнения, а затем иметь согласованное взаимодействие / поведение. Мы хотели бы иметь возможность стандартизировать директиву-оболочку, которая предоставляет эти вещи. Но шаблоны, которые мы используем для элементов списка (которые могут содержать внутренние директивы), различаются в зависимости от ситуации. И списки часто являются вложенными, рекурсивно, на основе древовидной структуры данных, которые их генерируют. Итак, нам нужна изоляция для этих вложенных списков. Мы контролируем весь код, поэтому мы не используем включение, чтобы изолировать внутреннее от внешнего. Скорее, нам нужен хороший декларативный подход к контейнерам и их произвольному содержимому, как это явно рекомендовано в Руководстве по директивам. Является ли ng-include лучшим вариантом для этого сейчас (передача пути к внутреннему шаблону в качестве атрибута в объявлении контейнера), даже если он уводит нас от встроенных шаблонов и, таким образом, кажется немного менее идиоматичным?

Я понимаю, что больше не работает, но я не вижу в журнале изменений рядом с этим выпуском какой-либо конкретной вещи, которая, кажется, коррелирует с этой конкретной проблемой.

https://github.com/angular/angular.js/blob/master/CHANGELOG.md#120 -timely-delivery-2013-11-08

Я имел в виду именно эти

  • передавать изолированную область только дочерним элементам, которые принадлежат директиве изолировать (d0efd5ee)
  • сделать изолирующий прицел действительно изолированным (909cabd3, # 1924, # 2500)

Но я уже не могу вспомнить, о чем я вообще говорил, кто знает ヽ ༼ ຈ ل͜ ຈ ༽ ノ

В журнале изменений есть только два типа сообщений: изменения и критические изменения. Это не считалось критическим изменением, а скорее исправлением ошибки. Было трудно предсказать, что многие люди использовали такое поведение. Но он, вероятно, должен войти в документы по миграции (которые нуждаются в некоторой любви)

Sho '' nuff. Это те. Я искал изменения, связанные с включением, но очевидно, что это изменение в области действия изолята, связанное с включением. Спасибо!

Это изменение также нарушило наш код. Было бы неплохо иметь простой декларативный способ доступа к переменным person этой директивы и использования их в родительской области. Похоже, это обычный вариант использования, если посмотреть, сколько людей использовали его до того, как этот bug был исправлен в 1.2.18.

Вот демонстрация, которая работает в 1.2.17 и не работает с 1.2.18.

http://plnkr.co/edit/QswOxN?p=preview

@evgenyneu : Предполагая, что вы контролируете как внутреннее содержимое, так и директиву контейнера, мы обнаружили, что использование ng-include вместо transclude понятно, легко и дает нам желаемый шаблон наследования. Мы просто передаем имя шаблона в качестве атрибута в объявлении внешней директивы и помещаем директиву ng-include в то же место, где раньше была директива transclude. Задача решена.

Если вы хотите, чтобы шаблон внутреннего содержимого был встроен в шаблон представления, просто используйте ng-template, чтобы обернуть шаблон в той же позиции, которую вы использовали раньше.

@xmlilley , отличный совет, большое вам спасибо. Это действительно самый чистый подход.

Вот демонстрация: http://plnkr.co/edit/4MwEL3?p=preview

Ребята, вы знаете, что вы можете просто сделать это прямо http://plnkr.co/edit/fw7thti1u4F9ArxsuYkQ?p=preview --- это не идеально, но это поможет вам

@caitp , милый, спасибо. У нас много решений!

Для тех, кто следит за этой проблемой, я создал «улучшенную» директиву ng-transclude, значение атрибута которой определяет искомую внутреннюю область видимости и может быть одним из трех:

  • silbing - область
  • parent - область включаемого содержимого - это область элемента, в котором происходит включение.
  • дочерний - область включенного содержимого является дочерней областью области элемента, в которой происходит включение.

Пример использования:

template: 
  '<div ng-transclude="parent">' +
  '</div>'

Полный пример

См. Этот пример для всего.

Результат выглядит так:

image

Источник

.config(function($provide){
    $provide.decorator('ngTranscludeDirective', ['$delegate', function($delegate) {
        // Remove the original directive
        $delegate.shift();
        return $delegate;
    }]);
})

.directive( 'ngTransclude', function() {
  return {
    restrict: 'EAC',
    link: function( $scope, $element, $attrs, controller, $transclude ) {
      if (!$transclude) {
        throw minErr('ngTransclude')('orphan',
         'Illegal use of ngTransclude directive in the template! ' +
         'No parent directive that requires a transclusion found. ' +
         'Element: {0}',
         startingTag($element));
      }

      var iScopeType = $attrs['ngTransclude'] || 'sibling';

      switch ( iScopeType ) {
        case 'sibling':
          $transclude( function( clone ) {
            $element.empty();
            $element.append( clone );
          });
          break;
        case 'parent':
          $transclude( $scope, function( clone ) {
            $element.empty();
            $element.append( clone );
          });
          break;
        case 'child':
          var iChildScope = $scope.$new();
          $transclude( iChildScope, function( clone ) {
            $element.empty();
            $element.append( clone );
            $element.on( '$destroy', function() {
              iChildScope.$destroy();
            });            
          });
          break;
      }
    }
  }
})

@Izhaki - FWIW, +10. Не нарушает текущие соглашения, но добавляет чистый декларативный способ добровольного доступа к общему варианту использования. Спасибо!

: +1: за решение @Izhaki . Я буду использовать его как есть, но мне было бы намного удобнее, если бы он был включен в angular.

+1

@Izhaki +1, отличный пример!

Спасибо за настраиваемую директиву включения. Я закончил тем, что просто сделал отдельное настраиваемое включение, вместо того, чтобы переопределять значение по умолчанию. В вашем патче есть несколько вызовов функций, и я не уверен, откуда они. minErr () и startTag ()

@dehru - эти функции являются внутренними для AngularJS.

@Izhaki большое спасибо! Я раздвоил ваш кусок, который повторяет включенный контент, если кому-то интересно.
Стоит отметить, что ng-transclude = "parent" может работать не так, как вы ожидаете.
http://plnkr.co/edit/S6ngqz?p=preview

plunker_ng-transclude_ng-repeat

@Izhaki Очень мило.
Похоже, что-то, что должно быть в запросе на перенос в angular.
По крайней мере, в отдельной директиве с репозиторием Github, как вы думаете, можно ли опубликовать ее (чтобы я мог предложить некоторые изменения ...)?
Спасибо

Ижаки, это круто. Я _heart_ вас.

@Izhaki Это очень хорошее решение проблемы детских прицелов. Спасибо.

Я запутался, и мне было интересно, можете ли вы объяснить, как наследование функции $ transclude переносится из внешней директивы в директиву ngTransclude? Это явно не указано в официальных документах, которые я мог найти, но я предположил, что transclude: true необходимо использовать в директиве для использования функции $ transclude в функции ссылки. Немного поиграв с кодом, я обнаружил, что использование transclude: true фактически нарушает код. Что тут происходит?

Я боролся с этим в течение нескольких дней, а также с тем фактом, что transclude вставляет включенные элементы в заполнитель transclude вместо того, чтобы заменять его ими.

Я обнаружил, что ng-transclude-replace решает обе проблемы! http://gogoout.github.io/angular-directives-utils/#/api/ng -directives-utils.transcludeReplace

Мне не нужно распространять директиву ng-repeat на саму повторяющуюся директиву или что-то в этом роде! Кажется, все работает, включая все проверки / привязки.

Вот фрагмент моего кода:

<form-field label="Roles" required>
    <checkbox-group>
        <checkbox ng-repeat="role in roles" label="{{role.description}}">
            <input type="checkbox" name="selectedRoles" ng-model="role.selected" value="{{role.description}}" ng-required="!hasRole" />
        </checkbox>
    </checkbox-group>
</form-field>

@abobwhite Спасибо за упоминание ng-transclude-replace, отличное исправление.

Я недавно столкнулся с этой проблемой. Хотя у меня работал прошедшее после этого обсуждения. В частности, был ли какой-нибудь интерес в том, чтобы сделать ng-transclude="sibling | parent | child" частью ядра angular?

@telekid - Сейчас мы не планируем включать эту функцию в ядро.

Я вижу, что есть решения и обходные пути, но было бы очень удобно, если бы существовал способ доступа к внутренней области действия директивы.

Я хотел бы отметить, что я обновил мод transclude, созданный включением нескольких слотов.
Филиал: https://github.com/NickBolles/ngTranscludeMod/tree/Angular1.5-multi-slot
PR: https://github.com/Izhaki/ngTranscludeMod/pull/2
Plunker: http://plnkr.co/edit/5XGBEX0muH9CSijMfWsH?p=preview

В итоге я просто использовал $parent . Это кладовая ванили без необходимости добавлять слишком много вещей.

Так что у меня что-то вроде:

angular.module('test').directive('myDirectiveWithTransclusion', function() {
     return {
          restrict: 'E'
          transclude: {
               transcludeThis: 'transcludeThis'
          }
          template: "<div ng-repeat='item in array'><div ng-transclude='transcludeThis'></div></div>"
     }
})
<my-directive-with-transclusion>
     <transclude-this>
          {{$parent.item}}
     </transclude-this>
</my-directive-with-transclusion>

Привет @ moneytree-doug: Я использую это решение, которое вы предоставили, но нахожу область действия transclude - это все еще область действия директивы, а не дочерний элемент новой области видимости, который создается ng-repeat. Вы можете мне что-нибудь посоветовать?

@szetin Не могли бы вы поставить jsfiddle, показывающий мне, чего вы ожидаете от того, что происходит?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги