Директива виділяє область застосування з ng-повторюваною сферою в AngularJS


95

У мене є директива з isolate-scope (щоб я міг повторно використовувати директиву в інших місцях), і коли я використовую цю директиву з an ng-repeat, вона не працює.

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

Тож я розумію, що мій код виходить з ладу через область дії, створену ng-repeatдирективою. Моя власна директива створює isolate-scope і робить двосторонню прив'язку даних до об'єкта в батьківській області. Моя директива призначить нове значення об’єкта цій прив’язаній змінній, і це чудово працює, коли моя директива використовується без ng-repeat(батьківська змінна оновлюється правильно). Однак, ng-repeatприсвоювання створює нову змінну в області ng-repeatдії, а батьківська змінна не бачить змін. Все це, як очікувалося, виходячи з прочитаного.

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

Тому я сподівався, що зможу використовувати пріоритет, щоб переконатися, що моя директива запускається першою і в кінцевому підсумку створює isolate-scope, а при ng-repeatзапуску вона повторно використовує isolate-scope замість того, щоб створювати область, яка прототипно успадковується від батьківської області. У ng-repeatдокументації зазначено, що ця директива працює на рівні пріоритету 1000. Незрозуміло 1, вищий рівень пріоритету чи нижчий. Коли я використовував рівень пріоритету 1в своїй директиві, це не мало значення, тому я спробував 2000. Але це погіршує ситуацію: мої двосторонні прив’язки стають, undefinedі моя директива нічого не відображає.

Я створив скрипку, щоб показати свою проблему . Я прокоментував priorityналаштування в моїй директиві. У мене є список об'єктів імен та директива, name-rowяка відображає поля першого та прізвища в об'єкті імені. Коли клацне відображене ім'я, я хочу, щоб воно встановило selectedзмінну в основній області. Масив імен, selectedзмінна передається name-rowдирективі за допомогою двостороннього прив'язки даних.

Я знаю, як змусити це працювати, викликаючи функції в основній області. Я також знаю, що якщо selectedвсередині іншого об'єкта, і я прив'язуюсь до зовнішнього об'єкта, все буде працювати. Але мене на даний момент ці рішення не цікавлять.

Натомість у мене є такі запитання:

  • Як я не можу ng-repeatстворити область, яка прототипно успадковується від батьківської області, і замість цього вона використовує isolate-scope моєї директиви?
  • Чому рівень пріоритету 2000в моїй директиві не працює?
  • Використовуючи Batarang, чи можна дізнатися, який тип сфери використовується?

1
Зазвичай ви не хочете використовувати область ізоляції, якщо ваша директива буде використовуватися на одному елементі з іншими директивами. Оскільки ви створюєте власні властивості області, і вам потрібно працювати з ng-repeat, я пропоную використовувати scope: trueдля вашої директиви. Див. Також (якщо ви ще цього не зробили) stackoverflow.com/questions/14914213/... Крім того, те, що директива буде використовуватися в кількох місцях, не означає, що ми повинні автоматично використовувати область ізоляції.
Марк Райчок

Я прочитав багато Ваших відповідей (вони чудові, дякую, що написали), але мені не спало на думку прочитати Ваші запитання :-). Я прочитав те, на що ви посилалися. Мені здається, що директиви з ізолятором не можна змішувати з іншими директивами. Я погоджуюся з думкою, що такі директиви є складовими, і тому їх не потрібно змішувати з іншими директивами. Єдиним винятком (поки що) для мене буде ng-repeat. Я думаю, що цінно мати можливість поєднувати окремі директиви з ng-repeat. Продовження ...
Діпак Нулу

Продовження зверху ... Отже, якщо для елемента повинна бути лише одна директива із сферою дії, то вона ng-repeatне повинна мати області дії. ng-repeatнаявність сфери дійсно має сенс для типового випадку використання, тому я не пропоную її змінювати. Натомість, як я прокоментував у відповіді Алекса Осборна, я думаю, що буду створювати повторювану директиву на основі ng-repeat, яка не створює власного обсягу. Потім це можна використовувати для повторення директив, які мають свої власні сфери ізоляції. Продовження ...
Діпак Нулу

Код, який повторює директиву, тепер повинен знати, чи використовувати, ng-repeatчи користувальницьку директиву повторення без обсягу. Я думаю, це нормально, якщо "абонент" знає це, але це не нормально, якщо "викликає" (директива повторюється) знає, повторюється вона чи ні. Продовження ...
Діпак Нулу

Трохи божеволіючи від коментарів тут ... :-) 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 завжди повинні мати a ..


Мої експерименти з директивами з isolate-scopes та двостороннім прив'язуванням даних приводять мене до думки, що .золоте правило потрібно лише для областей, які мають прототипне успадкування. У isolate-scopes змінні, які вас цікавлять, визначені у scope: {}визначенні в директиві, і тому ці змінні будуть існувати в isolate-scope з самого початку. Крім того, вони не маскують батьківські змінні, оскільки isolate-scope не прототипово успадковується від батьківської області. тобто не існує "батьківської" області для маскування.
Діпак Нулу

1
Слід також сказати: ваша директива не створює дочірню область, але її можна легко використовувати в контексті, який вимагає дочірньої області. ngRepeat - це один випадок. Так само як і трансклюзія. Повірте мені - використовуйте ..
Джош Девід Міллер

1
Дискусія щодо групи Google AngularJS, де @JoshDavidMiller нарешті зміг очистити мою плутанину!
Deepak Nulu

2
Звідки ви, хлопці, берете всі ці круті схеми? Я бачив, як їх публікував ще один користувач.
Асад Саедуддін,

3
@Asad, я нещодавно розмістив інструмент на GitHub, він називається Peri $ scope .
Mark Rajcok,

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.