Как написать директиву в AngularJS, как мне решить, не нужна ли мне новая область, новая дочерняя область или новая изолированная область?

265

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

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

Я надеюсь, что некоторые из команды Angular-UI (или другие, которые написали много директив) могут поделиться своим опытом.

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

Марк Райкок
источник
под «дочерней областью» вы подразумеваете создание области действия в функции ссылки под «областью действия. $ new ()»? Поскольку я, как известно, директива может иметь изолированную область видимости или не иметь ее (поэтому я буду использовать область видимости там, где она используется)
Валентин Шибанов
3
@ValentynShybanov Параметр scope: trueсоздаст дочернюю область $scope.new()автоматически.
Джош Дэвид Миллер
2
@Valentyn, что сказал Джош: так, есть три возможности scope: false(по умолчанию, без новой области видимости), scope: true(новая область видимости, которая наследуется по прототипу) и scope: { ... }(новая область выделения).
Марк Райкок
1
Да, спасибо. Я пропустил эту разницу между "истиной" и "{}". Хорошо знать.
Валентин Шибанов,
Существует 4-й случай, который люди обычно игнорируют ... это "управляющий директивами" ... Я думаю, что вопрос должен быть расширен, чтобы включить их ... +1 к вопросу ..
ganaraj

Ответы:

291

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

Высотная предпосылка: scope используется как «клей», который мы используем для связи между родительским контроллером, директивой и шаблоном директивы.

Родительская Область: scope: false так что никакой новой области вообще нет

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

В качестве примера я недавно создал директиву, которая рисует (статическую) векторную графику с использованием библиотеки SVG, которую я нахожусь в процессе написания. Он имеет $observeдва атрибута ( widthи height) и использует их в своих вычислениях, но не устанавливает и не читает никаких переменных области и не имеет шаблона. Это хороший вариант использования, чтобы не создавать другую область; нам не нужен, так зачем?

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

Детская сфера: scope: true

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

Очевидно, что ключевым преимуществом этого по сравнению с изолированной областью является то, что пользователь может свободно использовать интерполяцию для любых атрибутов, которые они хотят; Например, использование class="item-type-{{item.type}}"директивы с изолированной областью не будет работать по умолчанию, но прекрасно работает с директивой с дочерней областью, потому что все, что интерполируется, все еще может быть найдено по умолчанию в родительской области. Кроме того, сама директива может безопасно оценивать атрибуты и выражения в контексте своей собственной области, не беспокоясь о загрязнении или повреждении родительского объекта.

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

Я чаще использую детские прицелы, чем одиночные или родительские.

Изолировать область: scope: {}

Это для многоразовых компонентов. :-)

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

Чтобы быть более конкретным, все, что необходимо для этой автономной функциональности, предоставляется через определенные атрибуты, оцениваемые в контексте родительской области; это либо односторонние строки ('@'), либо односторонние выражения ('&'), либо двусторонние привязки переменных ('=').

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

Хорошей практикой является исключение как можно большего количества материалов на основе шаблонов из ссылки директивы и функций контроллера. Это обеспечивает другую «API-подобную» конфигурационную точку: пользователь директивы может просто заменить шаблон! Все функциональные возможности остались прежними, и его внутренний API никогда не затрагивался, но мы можем связываться со стилем и реализацией DOM столько, сколько нам нужно. UI / bootstrap - отличный пример того, как сделать это хорошо, потому что Peter & Pawel потрясающие.

Области применения изолята также отлично подходят для использования с включением. Взять вкладки; они представляют собой не только всю функциональность, но все, что находится внутри, может свободно оцениваться из родительской области, оставляя вкладки (и панели), чтобы делать все, что они хотят. Вкладки явно имеют свое собственное состояние , которое принадлежит области (для взаимодействия с шаблоном), но это состояние не имеет ничего общего с контекстом, в котором оно использовалось - оно полностью внутреннее того, что делает директиву tab директивой tab. Кроме того, нет смысла использовать какие-либо другие директивы с вкладками. Это вкладки - и мы уже получили эту функциональность!

Окружите его большей функциональностью или включите больше функциональности, но эта директива уже есть.

Из всего сказанного следует отметить, что существуют некоторые способы обойти некоторые ограничения (то есть функции) изолированной области видимости, как намекал @ProLoser в своем ответе. Например, в разделе дочерней области я упомянул интерполяцию при разрыве ненаправленных атрибутов при использовании изолированной области (по умолчанию). Но пользователь может, например, просто использовать, class="item-type-{{$parent.item.type}}"и это будет снова работать. Поэтому, если есть веская причина для использования изолированной области над дочерней областью, но вы беспокоитесь о некоторых из этих ограничений, знайте, что при необходимости вы можете обойти практически все из них.

Резюме

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

Я хотел высказать свои первоначальные мысли, но, как я думаю о других вещах, я буду обновлять это. Но святое дерьмо - это долго для ТАКОГО ответа ...


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

Джош Дэвид Миллер
источник
Спасибо, Джош, отличный ответ. Я хотел / ожидал длинных ответов для этого. Я не следовал двум вещам: 1) дочерняя область: «пользователь может использовать интерполяцию для любых атрибутов, которые он хочет». 2) выделить сферу: «или не все, в случае«? »» Можете ли вы рассказать об этом немного? (Не стесняйтесь редактировать свой пост вместо того, чтобы писать комментарии, если это проще.)
Марк Райкок
@MarkRajcok Для (1) я изменил его, чтобы сделать его немного менее туманным - дайте мне знать, если я потерпел неудачу. Для (2) это была комбинация опечатки и плохой формулировки; Я переписал этот абзац, чтобы было понятнее. Я также добавил дополнительный пример или два, уточнил еще несколько вещей и исправил некоторые опечатки.
Джош Дэвид Миллер
Как уже упоминалось в ответе - отличным примером их сочетания является начальная загрузка для angular. Я нашел пример аккордеона особенно полезным - GitHub - Аккордеон
CalM
Вы упомянули, что вы чаще всего используете дочерние области, я думал, что шаблон директив многократного использования был самым распространенным, и я избегал написания директив, которые должны были использоваться только один раз. Это не нужно? Иногда, когда мой HTML становится слишком большим, мне хочется переместить этот раздел в директиву, но он будет использоваться только один раз, поэтому я просто оставляю его в html.
user2483724
2
@ user2483724 Очень распространенное заблуждение состоит в том, что в директивах «многократного использования» используются изолированные области действия; не так. Если вы посмотрите на предварительно упакованные директивы, почти ни одна из них не использует изолированные области действия - некоторые даже не дочернюю область действия - но, уверяю вас, они могут использоваться повторно! Правило должно заключаться в том, как используется область действия внутри директивы. Если это просто экономия места в файле, я не уверен, что директива - лучший подход. Это увеличивает время обработки ради разработчика. Но если вам нужно, то пойти на это. Или используйте ngInclude. Или сделайте это как часть вашей сборки. Много вариантов!
Джош Дэвид Миллер
52

Моя личная политика и опыт:

Изолированный: частная песочница

Я хочу создать множество методов и переменных области видимости, которые используются ТОЛЬКО в моей директиве и никогда не видны и не доступны напрямую пользователю. Я хочу добавить в белый список данные о том, что мне доступно. Я могу использовать transclusion, чтобы позволить пользователю вернуться в родительскую область (без изменений) . Я НЕ хочу, чтобы мои переменные и методы были доступны для детей, прошедших заключение.

Ребенок: подраздел контента

Я хочу создать методы и области видимости переменных , которые МОГУТ быть доступны пользователю, но не имеют отношения к окружающим диапазонам (братьев , сестер и родителей) вне контекста моей директивы. Я также хотел бы, чтобы ВСЕ родительские данные области видимости просачивались прозрачно.

Нет: простые, доступные только для чтения директивы

Мне действительно не нужно связываться с методами или переменными области видимости. Я, вероятно, делаю что-то, что не связано с областями действия (например, отображение простых плагинов jQuery, проверка и т. Д.).

Ноты

  • Вы не должны позволять ngModel или другим вещам напрямую влиять на ваше решение. Вы можете обойти странное поведение, делая такие вещи, как ng-model=$parent.myVal(ребенок) или ngModel: '='(изолировать).
  • Isolate + transclude восстановит все нормальное поведение в директивах одного уровня и вернется в родительскую область, поэтому не позволяйте этому влиять на ваше суждение.
  • Не связывайтесь с областью действия ни с одним, потому что это все равно, что помещать данные в область для нижней половины DOM, но не для верхней половины, что имеет смысл 0.
  • Обратите внимание на приоритеты директив (не приводите конкретных примеров того, как это может повлиять на ситуацию)
  • Внедрить сервисы или использовать контроллеры для связи через директивы с любым типом области действия. Вы также require: '^ngModel'можете посмотреть в родительских элементах.
ProLoser
источник
1
Возможно, я неправильно понял эту часть: «Isolate + transclude восстановит все нормальное поведение в директивах братьев и сестер». Смотрите этот плункер . Вам придется заглянуть в консоль.
Джош Дэвид Миллер
1
Спасибо ProLoser за ваши идеи / ответ. Вы один из тех, на кого я надеялся увидеть этот пост, если бы я добавил тег angularjs-ui.
Марк Райкок
@JoshDavidMiller, когда речь идет о директивах для одного и того же элемента DOM, все усложняется, и вместо этого вы должны начать смотреть на свойство priority. Трансклюзия больше относится к содержанию детей.
ProLoser
1
@ProLoser Правильно, но я не уверен, что вы имели в виду под этим утверждением. Они явно влияют на детей, но как области действия директив влияют на их директивы братьев и сестер вообще?
Джош Дэвид Миллер
18

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

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

Если директива, которую вы собираетесь написать, будет просто делать доменные манипуляции, которые не нуждаются во внутреннем состоянии области видимости или явных изменениях области видимости (в основном очень простые вещи); не идти за новой областью . (таких , как ngShow, ngMouseHover, ngClick, ngRepeat)

Если директива, которую вы собираетесь написать, должна изменить некоторые элементы в родительской области, но также должна обрабатывать некоторое внутреннее состояние, перейдите к новой дочерней области . (такой как ngController)

Обязательно ознакомьтесь с исходным кодом директив: https://github.com/angular/angular.js/tree/master/src/ng/directive.
Это очень помогает в том, как о них думать.

Умур Контачи
источник
Если нескольким компонентам необходимо взаимодействовать друг с другом, они могут иметь изолированную область действия и использование require, поэтому ваши директивы остаются разъединенными. Так как же это ограничивает возможности? Это еще более делает директивы более конкретными (поэтому объявляйте, от чего вы зависите). Поэтому я бы оставил только одно правило: если ваша директива имеет состояние или нуждается в каких-либо данных из области видимости, где она используется - используйте изолированную область. В противном случае не используйте область. И о «детских сферах» - я также написал довольно много директив и никогда не нуждался в этой функции. Если «необходимо изменить некоторые элементы в родительской области» - используйте привязки.
Валентин Шибанов,
А также о «необходимости изменить некоторые элементы в родительской области» - если вы изменяете что-либо в дочерней области, изменения не заполняются в родительской области (если вы не используете грязный $parentхак). Так на самом деле «ребенок» прицелы для директив является то , что выглядит как следует использовать довольно сзади - как ngRepeatэто создает новые дочерние прицелы для каждого элемента , чтобы повторить (но это также создает его с помощью scope.$new();и не scope: true.
Валентина Shybanov
1
Вы не можете запрашивать несколько изолированных областей в одном и том же элементе, вы не можете получить доступ к функциям в родительской области, если вы не связываете их явно. (Удачи в использовании ngClickи т. Д.) Требование создает своего рода разделение, я согласен, но вам все равно нужно знать о родительской директиве. Если это не компонент , я против изоляции. Директивы (по крайней мере, большинство из них) предназначены для многократного использования, и изоляция нарушает это.
Умур Контачи
Я также не использую дочерние области в директивах, но так как дочерняя область прототипически наследуется от родительской области, если доступ к свойству в пределах свойства в родительской области, изменения заполняются. Авторы Angular говорили об этом на встрече MTV, «хорошо иметь где-то точку» youtube.com/watch?v=ZhfUv0spHCY
Umur Kontacı
5
Во-первых, я думаю, что вы слишком суровы с изолированными прицелами. Я думаю, что они имеют более широкое применение, чем вы им доверяете, и что есть способы избежать многих проблем, на которые вы (правильно) указали, с которыми мы сталкиваемся при их использовании. Я также не согласен с «не так много настроек для конечного разработчика» - подробности см. В моем ответе. Тем не менее, ваш ответ не был ни плохим, ни неправильным, и он действительно затрагивал вопрос, поэтому я не уверен, почему за него проголосовали. Итак, +1.
Джош Дэвид Миллер
9

Просто подумал, что я добавлю свое текущее понимание и как это связано с другими концепциями JS.

По умолчанию (например, не объявлено или область действия: false)

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

объем:{}

Это похоже на модуль, все, что он хочет использовать, должно быть передано явно. Если КАЖДАЯ директива, которую вы используете, является изолированной областью, это может быть эквивалентно созданию КАЖДОГО JS-файла, в котором вы пишете свой собственный модуль с большими затратами на внедрение всех зависимостей.

сфера: ребенок

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


Ключ в том, что ЛЮБУЮ директиву можно написать ЛЮБОМ способом. Различные декларации области видимости помогут вам в организации. Вы можете сделать все модулем или просто использовать все глобальные переменные и быть очень осторожным. Для простоты обслуживания предпочтительнее модулировать вашу логику на логически последовательные части. Существует баланс между открытым полем и закрытой тюрьмой. Причина, по которой это сложно, я считаю, что когда люди узнают об этом, они думают, что узнают о том, как работают директивы, но на самом деле они изучают организацию кода / логики.

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

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

user2483724
источник
2

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

В конце концов, это слишком странно - передавать каждое глобальное или разделяемое значение по цепочке с несколькими атрибутами при каждом вызове DOM директивы (как это требуется для изолированной области). Просто выглядит глупо многократно писать все это в DOM, и это кажется неэффективным, даже если это общие объекты. Это также излишне усложняет декларации директивы. Обходной путь использования $ parent для «достижения» и получения значений из директивы HTML выглядит как очень плохая форма.

Я также закончил тем, что изменил свое приложение, чтобы иметь в основном дочерние директивы области видимости с очень небольшим количеством изолятов - только те, которым не требуется доступ НИЧЕГО от родителя, кроме тех, которые они могут быть переданы через простые неповторяющиеся атрибуты.

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

- D

Ungallery
источник