Область действия директивы с областью ng-repeat в AngularJS

95

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

Я прочитал всю документацию и ответы на Stack Overflow по этой теме и понимаю проблемы. Я считаю, что избежал всех обычных ошибок.

Итак, я понимаю, что мой код не работает из-за области, созданной ng-repeatдирективой. Моя собственная директива создает изолированную область и выполняет двустороннюю привязку данных к объекту в родительской области. Моя директива назначит новое значение объекта этой связанной переменной, и это отлично работает, когда моя директива используется без ng-repeat(родительская переменная обновляется правильно). Однако с ng-repeatприсваиванием создается новая переменная вng-repeat видимости, и родительская переменная не видит изменений. Все это, как и ожидалось, исходя из того, что я прочитал.

Я также читал, что когда есть несколько директив для данного элемента, создается только одна область. И что a priorityможет быть установлен в каждой директиве, чтобы определить порядок, в котором директивы применяются; директивы сортируются по приоритету, а затем вызываются их функции компиляции (ищите приоритет слова на http://docs.angularjs.org/guide/directive ).

Поэтому я надеялся, что смогу использовать приоритет, чтобы убедиться, что моя директива запускается первой и заканчивается созданием изолированной области, а при ng-repeatзапуске она повторно использует изолированную область вместо создания области, которая прототипически наследуется от родительской области. В ng-repeatдокументации указано, что эта директива работает с уровнем приоритета 1000. Неясно, 1является ли уровень приоритета более высоким или более низким. Когда я использовал уровень приоритета 1в своей директиве, это не имело значения, поэтому я попытался 2000. Но это усугубляет ситуацию: мои двусторонние привязки становятся, undefinedа моя директива ничего не отображает.

Я создал скрипку, чтобы показать свою проблему . Я закомментировал priorityнастройку в своей директиве. У меня есть список объектов имени и вызванная директива, name-rowкоторая показывает поля имени и фамилии в объекте имени. При щелчке отображаемого имени я хочу, чтобы оно установило selectedпеременную в основной области. Массив имен, selectedпеременная передаются в name-rowдирективу с помощью двусторонней привязки данных.

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

Вместо этого у меня есть следующие вопросы:

  • Как мне предотвратить ng-repeatсоздание области, которая прототипически наследуется от родительской области, и вместо этого использовать мою область действия директивы?
  • Почему уровень приоритета 2000в моей директиве не работает?
  • Можно ли с помощью Батаранга узнать, какой тип прицела используется?
Дипак Нулу
источник
1
Обычно вы не хотите использовать изолированную область видимости, если ваша директива будет использоваться в одном элементе с другими директивами. Поскольку вы создаете свои собственные свойства области видимости и вам нужно работать с ng-repeat, я предлагаю использовать scope: trueдля вашей директивы. См. Также (если вы еще этого не сделали) stackoverflow.com/questions/14914213/. Кроме того, тот факт , что директива будет использоваться в нескольких местах, не означает, что мы должны автоматически использовать изолированную область.
Mark Rajcok
Я прочитал многие из ваших ответов (они превосходны, спасибо за их написание), но мне никогда не приходило в голову прочитать ваши вопросы :-). Я читал то, на что вы ссылались. Мне кажется, что директивы изолированной области нельзя смешивать с другими директивами. Я согласен с мнением, что такие директивы являются компонентами, и поэтому их не нужно смешивать с другими директивами. Единственным исключением (пока) для меня было бы ng-repeat. Я считаю ценной возможность сочетать отдельные директивы с ng-repeat. Продолжение следует ...
Deepak Nulu
Продолжение выше ... Итак, если должна быть только одна директива с областью действия для элемента, тогда ng-repeatне должно быть области действия. ng-repeatналичие области действия имеет смысл для типичного варианта использования, поэтому я не предлагаю ее менять. Вместо этого, как я прокомментировал в ответе Алекса Осборна, я думаю, что создам повторную директиву, основанную на ng-repeatтом, что она не создает свою собственную область. Затем это можно использовать для повторения директив, которые имеют свои собственные области действия. Продолжение следует ...
Deepak Nulu
Код, который повторяет директиву, теперь должен знать, использовать ng-repeatли настраиваемую директиву повторения без области действия. Я думаю, что для «вызывающего» это нормально, но для «вызываемого» (повторяющаяся директива) недопустимо знать, повторяется это или нет. Продолжение следует ...
Deepak Nulu
Немного сумасшедший с комментариями здесь ... :-) ngRepeat должен создать свою собственную область видимости. Как вы думаете, зачем вам здесь изолирующий прицел?
Джош Дэвид Миллер

Ответы:

203

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

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

Вот пример того же кода, но с удаленной директивой:

<li ng-repeat="name in names"
    ng-class="{ active: $index == selected }"
    ng-click="selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

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

Почему не работает? Поскольку области видимости в AngularJS используют прототипное наследование. Значение selectedв вашей родительской области является примитивным . В JavaScript это означает, что он будет перезаписан, когда дочерний элемент установит то же значение. В области видимости AngularJS есть золотое правило: значения модели всегда должны содержать .в себе. То есть они никогда не должны быть примитивными. См. Этот SO-ответ для получения дополнительной информации.


Вот изображение того, как изначально выглядят прицелы.

введите описание изображения здесь

После щелчка по первому элементу области теперь выглядят следующим образом:

введите описание изображения здесь

Обратите внимание, что новое selectedсвойство было создано в области ngRepeat. Область действия контроллера 003 не была изменена.

Вы, наверное, догадались, что происходит, когда мы нажимаем на второй элемент:

введите описание изображения здесь


Таким образом, ваша проблема на самом деле вообще не вызвана ngRepeat - она ​​вызвана нарушением золотого правила в AngularJS. Чтобы исправить это, просто используйте свойство объекта:

$scope.state = { selected: undefined };
<li ng-repeat="name in names"
    ng-class="{ active: $index == state.selected }"
    ng-click="state.selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

Вот второй JSFiddle, показывающий, что это тоже работает.

Вот как изначально выглядят прицелы:

введите описание изображения здесь

После нажатия на первый элемент:

введите описание изображения здесь

Здесь, при желании, изменяется область действия контроллера.

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

Первоначально объемы:

введите описание изображения здесь

Области действия после нажатия на первый элемент:

введите описание изображения здесь

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

Джош Дэвид Миллер
источник
Мои эксперименты с директивами с изолированными областями видимости и двухсторонней привязкой данных привели меня к мысли, что .золотое правило требуется только для областей видимости с прототипным наследованием. С изолированными областями видимости интересующие вас переменные определены в scope: {}определении в директиве, и поэтому эти переменные будут существовать в области изолированной области с самого начала. Кроме того, они не маскируют родительские переменные, потому что изолирующая область не наследуется прототипом от родительской области. т.е. нет никакой "родительской" области для маскировки.
Deepak Nulu
1
Следует также сказать: ваша директива не создает дочернюю область видимости, но ее можно легко использовать в контексте, требующем дочерней области. ngRepeat - один из примеров. Так что это включение. Поверьте мне - используйте расширение ..
Джош Дэвид Миллер
1
Обсуждение в группе Google AngularJS, где @JoshDavidMiller наконец смог развеять мое замешательство!
Deepak Nulu
2
Откуда у вас все эти крутые диаграммы? Я видел, как еще один пользователь их размещал.
Асад Саидуддин 08
3
@Asad, я недавно разместил инструмент на GitHub, он называется Peri $ scope .
Марк Райкок
6

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

http://jsfiddle.net/dVPLM/

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

В вашем шаблоне:

    <name-row 
        in-names-list="names"
        io-selected="selected">
    </name-row>

В вашей директиве:

    template:
'        <ul>' +      
'            <li ng-repeat="name in inNamesList" ng-class="activeClass($index)" >' +
'                <a ng-click="setSelected($index)">' +
'                    {{$index}} - {{name.first}} {{name.last}}' +
'                </a>' +
'            </li>' +
'        </ul>'

В ответ на ваши вопросы:

  • ng-repeat создаст область действия, вам действительно не следует пытаться это изменить.
  • Приоритет в директивах - это не просто порядок выполнения - см .: AngularJS: как компилятор HTML устанавливает порядок компиляции?
  • В Batarang, если вы проверите вкладку производительности, вы можете увидеть выражения, привязанные к каждой области, и проверить, соответствует ли это вашим ожиданиям.
Алекс Осборн
источник
Спасибо за такой быстрый ответ. Если то, что я пытаюсь сделать, идет вразрез с тоном, я немного опечален (AngularJS, тем не менее, отличный фреймворк). Директива предназначена для многократного использования. Когда он написан, автору не нужно беспокоиться о том, будет ли он использоваться с ng-repeat. Может быть, когда он впервые написан, он никогда не используется с ng-repeat. И когда-нибудь в будущем он может привыкнуть ng-repeat, и в этот момент он должен просто работать без перезаписи. Надеюсь, что в будущем выпуске AngularJS это станет возможным.
Deepak Nulu
Хотел бы уточнить свой комментарий выше. Я думаю, что можно написать директиву, которая не беспокоится о том, используется она ng-repeatили нет. Но похоже, что мне пришлось бы передать функцию в директиву, чтобы она могла изменять переменные в родительской области, вместо того, чтобы иметь возможность изменять двустороннюю привязку в собственной области директивы. Двусторонняя привязка и многоразовые директивы - две мои самые любимые вещи в AngularJS, и они ng-repeatоказались ложкой дегтя. Может быть, я смогу написать ng-repeatэквивалент, не создающий собственной области видимости.
Deepak Nulu