что такое binding или двустороннее связывание
Привязка данных в Angular (binding)
Итак, в предыдущих разделах мы подготовили проект к практической работе. В данном разделе мы рассмотрим, что такое привязка данных («байндинг», от англ. binding — связывание) в Angular и как ее делать.
Привязки используются для передачи каких-либо данных из компонента в шаблон и обратно. Есть несколько типов привязок в Angular:
Строчная интерполяция
Протестируем первый вид привязки. Изменим код шаблона, а именно его заголовок:
Затем изменим файл компонента и добавим переменную title
Теперь при изменении значения переменной title в компоненте, изменения также мгновенно будут отображаться в шаблоне. Попробуйте задать разные значения для переменной title и проверьте результат.
Также в такой привязке можно передавать не только переменную, а и, скажем, функцию, которая будет возвращать какое-то значение, или просто выполнять арифметические операции.
Привязка свойств и событий
Теперь сделаем второй и третий тип привязки. Добавим в разметку следующий элемент:
Для большей наглядности привязки перенесены на новую строку.
В файле компонента сделаем следующие изменения:
Теперь рассмотрим, что мы изменили.
Следующий input с типом text и кнопка также зависят от значения флага isChecked — если чекбокс отмечен (т.е. isChecked = true), тогда срабатывает привязка к атрибуту disabled и эти элементы отключаются. Как только вы снимите чекбокс, они станут доступны. Также к текстовому полю, к атрибуту есть привязка переменной с заголовком [value]=’title’
Еще одна привязка в этом примере сделана к кнопке — при клике на кнопку срабатывает метод changeTitle() и наш заголовок меняется на ‘Новый тайтл’.
Таким же способом можно сделать и привязку к атрибуту [class] или [style] любого тега
Двусторонняя привязка
А теперь давайте сделаем двухстороннюю привязку и будем менять заголовок на то значение, которое будем вводить в поле input.
Давайте для начала подготовим наш проект. В файле шаблона компонента нужно заменить эти строки, вместо [value] вставляем директиву [(ngModel)]
Далее в файле app.module.ts нам обязательно нужно подключить FormsModule
Как работает двухсторонний биндинг в современных JavaScript-фреймворках
Содержание статьи
Что такое двухсторонний биндинг данных?
JavaScript позволяет построить интерактивное взаимодействие с пользователем за счет реакции на его действия визуальными событиями. Человек вводит данные в форму, нажимает кнопку «Отправить», на странице появляется индикатор загрузки, а после (предположим, что была допущена ошибка) неправильно заполненные поля подсвечиваются красным. В этот момент под капотом приложения происходит примерно следующее:
Классический JS + jQuery код работал бы примерно следующим образом:
WARNING
В пункте 7 есть огромный риск нарушить инкапсуляцию — мы начинаем модифицировать представление. Где должна храниться эта логика? Как избежать ее дублирования с тем, что сгенерировало страницу изначально? Обычно именно на этом месте большинство фронтенд-кода превращается в лапшу.
Двухсторонний биндинг данных избавляет нас от шагов 2, 5 и 7, попутно однозначно решая проблему инкапсуляции логики представления (или, как сейчас модно считать, избавляет представление от логики вовсе).
Чтобы отобразить такой объект в нужную нам форму, понадобится примерно вот такой шаблон (нотация Underscore Template):
Теперь если каким-то чудом при любом изменении нашего input его значение будет автоматически попадать в entity.name и, наоборот, при изменении чего угодно в entity этот шаблон будет обновляться необходимым образом — это и будет двухсторонний биндинг данных. Наше представление полностью описывает логику отображения исходя из возможного состояния, а само приложение, не думая о DOM, это состояние меняет.
Проблемы реализации
К сожалению, пример выше — просто пример. В реальной жизни ему не хватает очень большого количества метаинформации, без которой подобный биндинг попросту не заработает. Чтобы все заработало в универсальном случае, нам надо разобраться хотя бы со следующим:
Для решения всех этих проблем каждый фреймворк предлагает свои костыли — уникальные решения, которые вносят ложку дегтя в такую красивую теорию. Самое время вставить наш микроскоп поглубже в каждый из них и понять, что же двухсторонний биндинг данных представляет собой на самом деле и откуда берутся некоторые, порой такие странные ограничения привычных нам инструментов.
Смотреть мы будем на три примера: * Angular — как канонический пример «нового лучшего HTML»; * Ember — как пример привязки более классической парадигмы JS к новому инструменту и, конечно, * Joosy — как живая демонстрация моего субъективного видения удобного двухстороннего биндинга.
Отслеживание изменений объекта
К сожалению, никаких универсальных способов отследить любые изменения объекта в JS попросту нет. Все существующие решения накладывают ограничения на то, как с объектом производится работа. А решений существует целых два: работа через сеттеры/геттеры и внешний мониторинг.
Сеттеры/геттеры (Ember, Joosy)
Ember
Геттеры и сеттеры являются центральным стержнем системы свойств Ember. Они позволяют не только оповещать об изменении объекта, но и подписываться на изменение конкретных полей. В базе все выглядит именно так, как было описано:
HISTORY
Если понаблюдать, как развиваются фреймворки, то мы увидим один забавный повторяющийся виток. Комплексные фреймворки а-ля Rails потихоньку разбиваются на множество независимых компонентов, которые можно использовать отдельно. Объекты с отслеживанием изменений — прекрасный кандидат на вынос из Ember/Joosy в независимую библиотеку с выделенным API.
Joosy
Геттеры и сеттеры для объектов в Joosy работают совершенно идентичным образом за тем лишь исключением, что в Joosy отсутствует подписка на изменение конкретного поля. Joosy не умеет следить за полями, он следит только за изменением сущности в целом (правда, событие об изменении все-таки содержит информацию о том, что же изменилось). За счет этого массивы организованы чуть проще:
Кроме этого, для хешей Joosy (мимикрируя под Ruby) дает возможность прямо объявить необходимые свойства.
Внешний мониторинг (Angular)
Архитектура Angular вводит «цикл отрисовки», одним из шагов которого является проверка по указанному списку отслеживания — а не изменилось ли чего. К каждому элементу списка отслеживания можно прицепить одну или несколько функций, которые вызовутся, как только значение изменится. Такое решение прозрачно работает с любыми способами установки атрибута (нет нужды в set или поддерживании списка полей), но и это, увы, не серебряная пуля.
А вообще, просто попробуй поискать angular watch на StackOverflow.com, и все сразу станет на свои места.
Секционирование страницы
Декларативные шаблоны (Angular, Ember)
Одна из причин, по которым Angular нужен «новый HTML», а Ember — Handlebars, именно в этом. Декларативное описание, которое они разбирают своим собственным внутренним парсером, дает им информацию о контекстах биндингов.
По этой же логике работают условия ( ng_if и <% raw %><
Флагом такого подхода является девиз: «Шаблоны не должны содержать логику!» Хотя я, откровенно говоря, считаю, что это как раз следствие, а не правило. В подобную декларативную нотацию уж очень накладно было бы встраивать полноценный логический язык. А так и волки сыты, и овцы целы. И язык не нужен, и к высшей цели пришли — вроде как логика в шаблонах — это плохо?
В реальности все не так просто. Логика не может испариться из твоего приложения, где-то она все равно должна быть. И если ее нет напрямую в шаблонах, то она должна попадать в шаблон в виде состояния. Это значит, что на каждый чих нам понадобится виртуальное состояние. Обработка индикаторов загрузки, условий доступности, факта «выбранности», абсолютно каждого мелкого переключателя. К сожалению, понимание, насколько серьезна эта плата, приходит уже в конце, когда приложение начинает обрастать мелкими удобствами.
Ручное секционирование (Joosy)
Мне всегда очень хотелось остаться с любимым HAML (в вариации с CoffeeScript), но таким образом, чтобы сохранить основные возможности двухстороннего биндига. Для достижения этой цели в Joosy используется ручное секционирование. На место декларативных объявлений пришли классические хелперы, набор которых позволяет определить регион и объявить его локальные объекты (при их изменении весь регион будет обновлен).
Например, чтобы достичь поведения, похожего на ng_repeat Angular или each Ember, можно написать что-то такое:
Теперь, когда поменяется @projects или любой проект из их числа, все автоматически окажется в DOM. Обрати внимание, что смотрители регионов специально сделаны таким образом, чтобы мониторить коллекцию с полной вложенностью. Поэтому сегмент на весь блок всего один.
Кроме инлайн-блоков, Joosy умеет рендерить в виде региона классический partial (прямо как в Rails). Это даже куда более частый случай.
Такой подход приводит к тому, что регионов в Joosy обычно гораздо меньше, чем в Angular или Ember. Практика показывает, что производительности это не мешает. Зато дает возможность работать с абсолютно любым языком шаблонов, с абсолютно любой логической нотацией и вручную управлять динамической привязкой (включая возможность завязать перерисовку региона на объект, который в нем не используется), что иногда бывает ох как полезно.
Минус — обратная сторона плюса, вручную всем управлять не только можно, но и необходимо. Нет объявленных регионов — нет двухстороннего биндинга. Другая теоретическая проблема — работа с огромными регионами (1000 строк в таблице). Так как в Joosy каждый массив создает всего один регион, обновление любого объекта этого массива приведет к полной перерисовке всего региона. В этом edge-случае по умолчанию хорошо себя ведет только Ember. Joosy и Angular потребуют ручной оптимизации биндинга.
Частичное обновление DOM
Теперь у нас есть регионы, которые ждут изменения своего набора объектов и автоматически перерисовываются. Жизнь вроде бы налаживается. Но есть еще одна проблема, которую надо решить:
Metamorph (Ember, Joosy)
Ember и Joosy используют одно решение. Изначально (Joosy писался параллельно с Ember) мы просто написали одно и то же. В итоге оказалось, что решение Ember очень удачно обернуто во внешнюю библиотеку, — и Metamorph, надо сказать, прекрасно справляется с поставленной задачей.
В современных браузерах Metamorph просто использует W3C Ranges, а вот в старых все куда интереснее. Регион оборачивается в два тега
Check Also
Змеиная почта. Пишем на Python и Qt почтовик с возможностью прикреплять файлы
Возможно, ты уже умеешь писать простые сценарии на Python, но пробовал ли ты делать графич…
Что такое двухстороннее связывание?
Я много читал, что Backbone не выполняет двустороннюю привязку, но я не совсем понимаю эту концепцию.
Может ли кто-нибудь дать мне пример того, как двухстороннее связывание работает в базе кода MVC и как это не работает с Backbone?
5 ответов
Двухстороннее связывание означает, что:
В Backbone вы можете легко достичь # 1, привязав метод представления «render» к событию «change» его модели. Чтобы достичь # 2, вам также нужно добавить прослушиватель изменений в элемент input и вызвать model.set в обработчике.
Вот скрипка с двусторонней привязкой, настроенной в Backbone.
Стоит упомянуть, что есть много разных решений, которые предлагают двухстороннее связывание и играют действительно хорошо.
На github есть более расширенный список расширений / плагинов для магистралей.
У МакГарнагла отличный ответ, и вы захотите принять его, но я подумал, что упомяну (так как вы спросили), как работает привязка данных.
Обычно это реализуется путем запуска событий всякий раз, когда в данные вносятся изменения, которые затем приводят к обновлению прослушивателей (например, пользовательского интерфейса).
Двухстороннее связывание работает, делая это дважды, с некоторой осторожностью, чтобы гарантировать, что вы не застрянете в цикле событий (когда обновление события вызывает другое событие, которое будет запущено).
Двухстороннее связывание означает, что любые связанные с данными изменения, влияющие на модель, немедленно распространяются в соответствующие представления и что любые изменения, вносимые в представления (ии) (скажем, пользователем) немедленно отражаются в базовой модели. Когда изменяются данные приложения, меняется и пользовательский интерфейс, и наоборот.
Хорошая реализация двустороннего связывания, очевидно, должна сделать эту связь между моделью и некоторыми представлениями как можно более простой, с точки зрения разработчика.
Это довольно типичный шаблон в необработанном приложении Backbone. Как видите, для этого требуется приличное количество (довольно стандартного) кода.
AngularJS и некоторые другие альтернативы (Ember, Knockout…) обеспечивают двустороннюю привязку в качестве функции от первого лица. Они абстрагируют многие крайние случаи в рамках некоторых DSL и делают все возможное для интеграции двухстороннего связывания в свою экосистему. Наш пример будет выглядеть примерно так с AngularJS (непроверенный код, см. Выше):
Но учтите, что некоторые полноценные расширения для двусторонней привязки действительно существуют и для Backbone (в необработанном, субъективном порядке уменьшения сложности): Эпоксидная смола, Stickit, ModelBinder…
Например, в Epoxy есть одна интересная особенность: она позволяет вам объявлять ваши привязки (атрибуты модели элемента DOM представления) либо внутри шаблона (DOM), либо внутри реализации представления (JavaScript). Некоторым людям очень не нравится добавлять «директивы» в DOM / шаблон (например, атрибуты ng- *, требуемые AngularJS, или атрибуты привязки данных Ember).
Взяв в качестве примера Epoxy, можно преобразовать необработанное приложение Backbone во что-то вроде этого (…):
На самом деле emberjs поддерживает двустороннюю привязку, которая является одной из самых мощных функций для инфраструктуры JavaScript MVC. Вы можете проверить это, упомянув binding в руководстве пользователя.
Для emberjs создать двухстороннюю привязку можно, создав новое свойство со строкой Binding в конце, а затем указав путь из глобальной области видимости:
Обратите внимание, что привязки не обновляются сразу. Ember ждет, пока весь код вашего приложения завершит работу, прежде чем синхронизировать изменения, поэтому вы можете изменять связанное свойство столько раз, сколько захотите, не беспокоясь о накладных расходах синхронизации привязок, когда значения временные.
Надеюсь, это поможет в расширении оригинального ответа.
Привязка
Введение в привязку данных
В WPF привязка (binding) является мощным инструментом программирования, без которого не обходится ни одно серьезное приложение.
Привязка подразумевает взаимодействие двух объектов: источника и приемника. Объект-приемник создает привязку к определенному свойству объекта-источника. В случае модификации объекта-источника, объект-приемник также будет модифицирован. Например, простейшая форма с использованием привязки:
Для определения привязки используется выражение типа:
Работа с привязкой в C#
В данном случае получаем привязку для свойства зависимостей TextProperty элемента myTextBlock.
Также можно полностью установить привязку в коде C#:
Если в дальнейшем нам станет не нужна привязка, то мы можем воспользоваться классом BindingOperations и его методами ClearBinding() (удаляет одну привязку) и ClearAllBindings() (удаляет все привязки для данного элемента)
Некоторые свойства класса Binding :
ElementName : имя элемента, к которому создается привязка
IsAsync : если установлено в True, то использует асинхронный режим получения данных из объекта. По умолчанию равно False
Mode : режим привязки
Path : ссылка на свойство объекта, к которому идет привязка
TargetNullValue : устанавливает значение по умолчанию, если привязанное свойство источника привязки имеет значение null
RelativeSource : создает привязку относительно текущего объекта
Source : указывает на объект-источник, если он не является элементом управления.
XPath : используется вместо свойства path для указания пути к xml-данным
Режимы привязки
Свойство Mode объекта Binding, которое представляет режим привязки, может принимать следующие значения:
OneWay : свойство объекта-приемника изменяется после модификации свойства объекта-источника.
OneTime : свойство объекта-приемника устанавливается по свойству объекта-источника только один раз. В дальнейшем изменения в источнике никак не влияют на объект-приемник.
OneWayToSource : объект-приемник, в котором объявлена привязка, меняет объект-источник.
Применение режима привязки:
Обновление привязки. UpdateSourceTrigger
Односторонняя привязка от источника к приемнику практически мгновенно изменяет свойство приемника. Но если мы используем двустороннюю привязку в случае с текстовыми полями (как в примере выше), то при изменении приемника свойство источника не изменяется мгновенно. Так, в примере выше, чтобы текстовое поле-источник изменилось, нам надо перевести фокус с текстового поля-приемника. И в данном случае в дело вступает свойство UpdateSourceTrigger класса Binding, которое задает, как будет присходить обновление. Это свойство в качестве принимает одно из значений перечисления UpdateSourceTrigger :
PropertyChanged : источник привязки обновляется сразу после обновления свойства в приемнике
LostFocus : источник привязки обновляется только после потери фокуса приемником
Explicit : источник не обновляется до тех пор, пока не будет вызван метод BindingExpression.UpdateSource()
Свойство Source
Свойство Source позволяет установить привязку даже к тем объектам, которые не являются элементами управления WPF. Например, определим класс Phone:
Теперь создадим объект этого класса и определим к нему привязку:
Свойство TargetNullValue
На случай, если свойство в источнике привязки вдруг имеет значение null, то есть оно не установлено, мы можем задать некоторое значение по умолчанию. Например:
В данном случае у ресурса nexusPhone не установлено свойство Title, поэтому текстовый блок будет выводить значение по умолчанию, указанное в параметре TargetNullValue.
Свойство RelativeSource
Свойство RelativeSource позволяет установить привязку относительно элемента-источника, который связан какими-нибудь отношениями с элементом-приемником. Например, элемент-источник может быть одним из внешних контейнеров для элемента-приемника. Либо источником и приемником может быть один и тот же элемент.
Self : привязка осуществляется к свойству этого же элемента. То есть элемент-источник привязки в то же время является и приемником привязки.
FindAncestor : привязка осуществляется к свойству элемента-контейнера.
Например, совместим источник и приемник привязке в самом элементе:
Здесь текст и фоновый цвет текстового поля связаны двусторонней привязкой. В итоге мы можем увидеть в поле числовое значение цвета, поменять его, и вместе с ним изменится и фон поля.
Привязка к свойствам контейнера:
При использовании режима FindAncestor, то есть привязке к контейнеру, необходимо еще указывать параметр AncestorType и передавать ему тип контейнера в виде выражения AncestorType=
Свойство DataContext
Таким образом мы задаем свойству DataContext некоторый динамический или статический ресурс. Затем осуществляем привязку к этому ресурсу.
Понимание двустороннего связывания данных в AngularJS
В этом уроке я продемонстрирую, как двустороннее связывание данных работает в AngularJS путем создания динамического генератора визитных карточек. Этот генератор позволит вам создавать свои собственные виртуальные визитные карточки, которые вы можете персонализировать, указав свое имя, род занятий, электронную почту, логотип компании, а также ссылки на домашнюю страницу и сайты социальных сетей. Вы сможете отрегулировать как цвет фона, так и цвет текста карты, используя цветовые вводы HTML5, и увидеть все сделанные изменения на экране в режиме реального времени.
Вот чем мы закончим:
Начиная
Мы собираемся использовать Bower для управления зависимостями нашего проекта. Bower — это менеджер пакетов для Интернета, который можно установить с помощью npm (что означает, что вам нужно установить Node.js). Если вам нужна помощь в установке Node.js (или npm), ознакомьтесь с этой недавней статьей SitePoint на эту тему. Если вам нужна помощь в установке Bower, вы можете ознакомиться с инструкциями на их домашней странице.
Нашими зависимостями для этого проекта будут Bootstrap Framework (для стиля и аккордеонного компонента), Font Awesome (для иконок), а также jQuery (от которого зависит Bootstrap) и AngularJS.
Предполагая, что у вас установлен и настроен Bower, создайте новый каталог, cd в этот каталог и используйте Bower для инициализации проекта:
Bower создаст файл bower.json в корневом каталоге вашего проекта. Он также задаст вам несколько вопросов, таких как название проекта, имя автора, описание и так далее. Под именем введите «ACG» (для генератора угловых карт) и заполните все остальное, как считаете нужным (или просто примите значения по умолчанию). Результирующий файл JSON должен выглядеть следующим образом:
Далее выполните следующую команду в терминале: