Подтвердить что ты не робот

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

У меня есть текстовое поле в контроллере, который привязан к модели name. Там есть директива внутри контроллера и есть еще одно текстовое поле внутри директивы, которое привязано к той же модели name:

<div class="border" ng-controller="editCtrl">
   Controller: editCtrl <br/>
   <input type="text" ng-model="name" />
   <br/>
   <tabs>
      Directive: tabs <br/>
      <input type="text" ng-model="name"/>
   </tabs>
</div>

mod.directive('tabs', function() {
  return {
    restrict: 'E',
    transclude: true, 
    template:
      '<div class="border" ng-transclude></div>',
  };
});

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

См. пример: http://jsfiddle.net/uzairfarooq/MNBLd/

Я также попытался использовать двухстороннюю привязку attr (scope: {name: '='}), но она дает синтаксическую ошибку. И использование scope: {name: '@'} имеет тот же эффект.

Любая помощь будет принята с благодарностью.

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

4b9b3361

Ответ 1

Директива с transclude: true приводит к тому, что директива создает новую (трансключенную) дочернюю область. Эта новая область прототипа наследуется от родительской области. В вашем случае родительская область - это область, связанная с контроллером editCtrl.

Использование двухсторонней привязки данных в дочерней области (т.е. ng-model) для привязки к свойству родительской области, содержащему примитивное значение (например, name), всегда вызывает проблемы - ну, я должен сказать, что это не работает должным образом. Когда свойство области изменяется в дочернем элементе (например, вы вводите во второе текстовое поле), дочерний элемент создает новое свойство scope, которое скрывает/тени свойство родительской области с тем же именем. Если родительское свойство содержит примитивное значение, это значение (по существу) копируется в дочернее свойство при создании дочернего свойства. Будущие изменения в области содержимого (например, во втором текстовом поле) влияют только на дочернее свойство.

Прежде чем вводить во второе текстовое поле (т.е. перед изменением свойства в дочернем элементе), область child/transcluded находит свойство name в родительской области через прототипное наследование (пунктирная линия на рисунке ниже). Вот почему два текстовых поля изначально остаются в синхронизации. Ниже, если вы наберете "Марк" в первом текстовом поле, это выглядит так:

transcluded scope follows inheritance chain

Я создал fiddle, где вы можете изучить две области. Нажмите ссылку "показать область" рядом со вторым текстовым полем, прежде чем вводить текст во второе текстовое поле. Это позволит вам увидеть трансключенный охват ребенка. Вы заметите, что на данный момент у него нет свойства name. Очистите консоль, введите во второе текстовое поле и нажмите ссылку еще раз. Вы заметите, что дочерняя область теперь имеет свойство name, а начальное значение - это значение, которое имеет родительское свойство ( "Mark" ). Если вы ввели "нравится Angular" во второе текстовое поле, это выглядит так:

transcluded primitive hides parent property

Существует два решения:

  • сделайте то, что предлагает @pgreen2 (это решение "лучшей практики" ) - используйте объект вместо примитива. Когда объект используется, область child/transcluded не получает новое свойство. Здесь используется только прототипное наследование. На рисунке ниже предположим, что область editCtrl $имеет этот объект:
    $scope.myObject = { name: "Mark", anotherProp: ... }:
    object in parent
  • используйте дочерний объект $parent в области содержимого (это хрупкое решение и не рекомендуется, поскольку оно делает предположения о структуре HTML): используйте ng-model="$parent.name" внутри <input> , который находится внутри элемента <tabs> . Первое изображение выше показывает, как это работает.

Синтаксическая ошибка возникает при использовании scope: {name: '='}, потому что при использовании двусторонней привязки данных (т.е. при использовании '=') интерполяция недопустима, т.е. {{}} не может использоваться. Вместо <tabs name="{{name}}"> используйте <tabs name="name">.

Использование '@' работает так же, как и для случая transclude, поскольку ng-transclude использует transcluded scope, а не область выделения, которая создается с помощью scope: { ... }.

Для (лотов) больше информации о областям (включая картинки) см. Каковы нюансы объема прототипа/прототипического наследования в AngularJS?

Ответ 2

Я считаю, что проблема связана с определением области. Первоначально внутреннее текстовое поле не имеет набора name, поэтому оно наследуется от внешней области. Вот почему ввод текста во внешней коробке отражается во внутреннем поле. Однако после ввода во внутреннем поле внутренней области теперь содержится name, что означает, что она больше не привязана к внешнему name, поэтому внешнее текстовое поле не синхронизируется.

Соответствующий способ исправления заключается только в хранении моделей в области, а не в ваших значениях. Я исправил его в http://jsfiddle.net/pdgreen/5RVza/. Трюк заключается в создании объекта модели (data) и ссылки на него.

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

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

update: ссылка: https://www.youtube.com/watch?v=ZhfUv0spHCY#t=29m19s

Ответ 3

Синтаксическая ошибка означает, что вы что-то неправильно описали. Это не связано с конкретной структурой/библиотекой. Вероятно, вы забыли добавить "," или закрыть парантез. Проверьте его снова