Angular.js: ng-transclude не должен создавать новую одноуровневую область.

Созданный на 20 дек. 2013  ·  69Комментарии  ·  Источник: angular/angular.js

Это скорее запрос на изменение, и я хотел бы узнать, что думают другие люди.

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

Вот пример того, что, на мой взгляд, новая область действия не имеет смысла:

ui.directive('box', function() {
    return {
        restrict: 'E',
        transclude: true,
        template: '<div ng-transclude/>',
        replace: true,
        scope: {}
    };
});

Все, что требуется этой директиве, - это заменить <box>content</box> на <div>content</div> и содержимое, имеющее изолированную область видимости.

Создание такой вложенной структуры директив приводит к загрязнению дерева областей видимости. Вот пример плункера (http://plnkr.co/edit/DwukVGGprFFjQuVY8yTz) трех вложенных директив, которые создают такую ​​древовидную структуру области видимости:

< Scope (002) : ng-app
    < Scope (003) : ng-controller
        < Scope (004) : box
        < Scope (005) : ng-transclude
            < Scope (006) : box
            < Scope (007) : ng-transclude
                < Scope (008) : box
                < Scope (009) : ng-transclude

Такое поведение, похоже, не добавляет ценности своему назначению, но создает путаницу среди новичков.

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

ui.directive('box', function() {
    return {
        restrict: 'E',
        transclude: true,
        template: '<div/>',
        replace: true,
        scope: {},
        link: function(scope, element, attrs, transclude) {
            transclude(scope.$parent, function(content) {
                element.append(content);
            });
        }
    };
});

Вот пример плункера (http://plnkr.co/edit/46v6IBLkhS71L1WbUDFl), который иллюстрирует эту концепцию. Дерево областей видимости остается красивым и аккуратным:

< Scope (002) : ng-app
    < Scope (003) : ng-controller
        < Scope (004) : box
        < Scope (005) : box
        < Scope (006) : box

И двусторонняя привязка работает так, как многие ожидают, когда они связывают 'value', а не 'object.value'. (Я считаю, что тот факт, что в некоторых случаях передача просто значения работает, но не в других, и обвинение в природе прототипного наследования в javascript не является хорошим оправданием. Тот факт, что многие люди находят такое поведение неожиданным, указывает на наличие архитектурного недостатка .)

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

Lots of comments $compile high won't fix bug

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

Хорошо, что я перешел на Ember много лет назад. :)

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

Где transclude создает новую область видимости? http://plnkr.co/edit/EuHaBR26JgAegQKvwOGH?p=preview Я этого не вижу

Я говорю о директиве ng-transclude. То, что у вас есть в вашем примере, - это именно то, что делает моя работа.

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

мы должны учитывать это для 1.3

Хороший! Я рад, что вы уже об этом думали.

+1 за это. Я думаю, что моя проблема связана с этим: я использую ng-transclude в директиве с формами, и мне нужно пройти через область видимости. $$ childHead для доступа к объекту проверки формы, но у меня нет проблемы с доступом к моим моделям.

Вот пример: http://fiddle.jshell.net/39cgW/3/

+1 столкнулся с этой проблемой сегодня, и я ненавижу бросать везде $parent .

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

1) измените директиву ngTransclude, чтобы указать ее область действия (я думаю, что на самом деле ее можно уменьшить намного больше, чем это, я думаю - контроллер не нужен)

или же

2) не создавайте новую область, если область не указана

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


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

Мысли?

Я не понимаю разницы между вашим 1 и 2!

Просто мое мнение, но я думаю, что для этого изменения будет важна обратная совместимость. Я полагаю, что существует множество приложений (включая мое), которые помогли решить эту проблему с областью действия, используя такие вещи, как $ parent и scope. $$ childHead. Любое обновление, изменяющее это поведение, вызовет некоторые головные боли (но, возможно, лучше вызвать головную боль раньше, чем позже).

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

Все только мои мысли. По крайней мере, просто иметь возможность будет на шаг выше текущей ситуации. :)

@troch, чтобы уточнить, мы вводим следующее в контроллер ngTranscludeDirective:

        // This is the function that is injected as `$transclude`.
        function controllersBoundTransclude(scope, cloneAttachFn) {
          var transcludeControllers;

          // no scope passed
          if (arguments.length < 2) {
            cloneAttachFn = scope;
            scope = undefined;
          }

          if (hasElementTranscludeDirective) {
            transcludeControllers = elementControllers;
          }

          return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers);
        }

Директива вызывает эту функцию без области действия, и поэтому область действия не определена ... Затем в boundTranscludeFn она создает новую область действия, если transcludeScope является ложным ...

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

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

И то, и другое сделать довольно просто.

+1

+1

+1

Определенно +1

+1

+1

+1

+1 пожалуйста

Вы знаете, что было бы довольно круто, хотя, возможно, не полностью выполнимо во времени для 1.3, прокси ES6 могут сделать включение действительно приятным - не отставая от свойств области включения, но имея правильное положение в иерархии.

Если реализация Proxy недоступна, возможно, можно просто вернуться к scope. $ New (), так что на самом деле может быть возможно сделать эту работу довольно рано. Сложность в том, что характеристики немного шаткие.

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

+1

+1

+1

+1

+1

@caitp

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

Все, что не имеет критических изменений, получает мой голос.

+1

+1

+1

+1

+1

+1. Меня всегда интересовало такое поведение. Мне нравится это решение от caitp:

@caitp

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

+1

+1

+1

Для тех, кто следит за этой проблемой, я создал «улучшенную» директиву 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;
      }
    }
  }
})

Поскольку проблема, которую я поднял ранее в # 8609, была закрыта как дубликат этой темы, я повторяю ее здесь.

На мой взгляд, нынешний способ создания области видимости для включенной части модели DOM в высшей степени нелогичен!
Он идет против нормального потока в угловом режиме и должен быть исправлен!

Вот выдержка из моего предыдущего выпуска:

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

главный преступник находится в этой директиве

     function pane() {
        return {
           restrict: 'E',
           transclude: true,
           scope: {
              title: '@'
           },
           template: '<div style="border: 1px solid black;">' +
              '<div style="background-color: gray">{{title}} (isolate scope id: {{$id}})</div>' +
              '<ng-transclude></ng-transclude>' +
              '</div>'
        };
     }

Когда пользователь делает что-то вроде этого:

<form>
    <pane title='enter your name'>
         <input type='text ngModel='username'>
    </pane>
    <pane title='enter your token'>
         <input type='text ngModel='token'>
   </pane>


Результат удивит многих, особенно новых пользователей. И это даже (чрезмерно) упрощенный вариант использования. Попробуйте поместить туда отображение некоторых проверочных сообщений;)

Это другой вариант использования, в котором возникла эта проблема, но я согласен с тем, что это в основном та же проблема!

Вместо этого имеет смысл включить область видимости с помощью элемента dom. Если действительно нужна новая область, пользователь в любом случае может поместить ngController в элемент ngTransclude . Или может быть необязательный флаг на ngTransclude который запускает его. Этот флаг также можно использовать для решения варианта использования # 5489. И его также можно использовать для предлагаемого решения caitp.

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1 кажется, что директива transcluded создает область видимости, даже если ее просят не делать этого. пример http://plnkr.co/edit/Wn81IBkE87vtigXvjmIa?p=preview

+1

+1

+1

+1

+1

+1

+1

+1

+1 - есть ли какие-нибудь обходные пути для этого?

@nikkwong , в этой теме есть пара обходных путей. Я знаю, что сделал ссылку на листе, который показал обходной путь.

Хорошо, так что даже если он появится, его не будет до 1.5.x

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

Сравните этот плункер:
http://plnkr.co/edit/3NVxdYGy1AFDvD0M2BYI?p=preview

с версией, которая повторно использует область действия, из которой происходит исходное включение:
http://plnkr.co/edit/MXFz2awcqwQQ7R882Xwz?p=preview

Во второй версии, когда вы включаете и выключаете включенный контент, количество наблюдателей продолжает расти - утечка памяти.

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

Мы узнали, что проблема заключается в том, как работает двухсторонняя привязка angular, определенно ng-transclude создает дочернюю область, но будет множество случаев, когда вы обнаружите себя в той же ситуации, например, ng-repeat создает дочернюю область. Посмотрев на код angular, мы поняли, что проблема с двухсторонней привязкой не работает свойство с атрибутом объекта, но отлично работает с самими объектами.

Проблема: http://plnkr.co/edit/Wn81IBkE87vtigXvjmIa?p=preview

Решение: http://plnkr.co/edit/KShClgQVwIjscXPzVRwR?p=preview

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

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

@petebacondarwin Это будет лишь временная утечка, пока область удержания не будет уничтожена. Вот (кусок) [http://plnkr.co/edit/Q587WQnX0u0u7JjhtCxa?p=preview], который показывает, что если вы отключите transclude-holding-scope, все будет выпущено нормально.
Тем не менее, этот момент требует внимания. Возможно, для устранения этой возможной утечки будет достаточно большого внимания в документации к этой возможной утечке?

Ну, любая утечка JS носит временный характер, пока вы не обновите браузер ;-)
8 сентября 2015 года, 16:41, «Сандер Элиас» [email protected] написал:

@petebacondarwin https://github.com/petebacondarwin Это будет только
временная утечка, пока прицел не будет разрушен. Вот
plunk) [http://plnkr.co/edit/Q587WQnX0u0u7JjhtCxa?p=preview], который показывает
что если вы отключите transclude-holding-scope, все будет
выпущен просто отлично.
Тем не менее, этот момент требует внимания. Возможно, большой Варринг в
документов об этой возможной утечке может быть достаточно, чтобы исправить это?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/angular/angular.js/issues/5489#issuecomment -138603298
.

@SanderElias Пользователи нашего приложения Angular заняты с начала до конца дня.
Мы уже видим, что использование памяти увеличивается в течение дня, и мы должны быть очень осторожны с тем, что мы размещаем на странице, введение большего количества возможных утечек рискованно.

@troch -

Я должен согласиться с @petebacondarwin , и даже если текущее поведение не на 100% интуитивно

Хорошо, что я перешел на Ember много лет назад. :)

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