что такое two way binding
What is two way binding?
I have read lots that Backbone doesn’t do two way binding but I don’t exactly understand this concept.
Could somebody give me an example of how two way binding works in an MVC codebase and how it does not with Backbone?
6 Answers 6
Two-way binding just means that:
Backbone doesn’t have a «baked-in» implementation of #2 (although you can certainly do it using event listeners). Other frameworks like Knockout do wire up two-way binding automagically.
In Backbone, you can easily achieve #1 by binding a view’s «render» method to its model’s «change» event. To achieve #2, you need to also add a change listener to the input element, and call model.set in the handler.
Here’s a Fiddle with two-way binding set up in Backbone.
Two-way binding means that any data-related changes affecting the model are immediately propagated to the matching view(s), and that any changes made in the view(s) (say, by the user) are immediately reflected in the underlying model. When app data changes, so does the UI, and conversely.
This is a very solid concept to build a web application on top of, because it makes the «Model» abstraction a safe, atomic data source to use everywhere within the application. Say, if a model, bound to a view, changes, then its matching piece of UI (the view) will reflect that, no matter what. And the matching piece of UI (the view) can safely be used as a mean of collecting user inputs/data, so as to maintain the application data up-to-date.
A good two-way binding implementation should obviously make this connection between a model and some view(s) as simple as possible, from a developper point of view.
It is then quite untrue to say that Backbone does not support two-way binding: while not a core feature of the framework, it can be performed quite simply using Backbone’s Events though. It costs a few explicit lines of code for the simple cases; and can become quite hazardous for more complex bindings. Here is a simple case (untested code, written on the fly just for the sake of illustration):
This is a pretty typical pattern in a raw Backbone application. As one can see, it requires a decent amount of (pretty standard) code.
AngularJS and some other alternatives (Ember, Knockout…) provide two-way binding as a first-citizen feature. They abstract many edge-cases under some DSL, and do their best at integrating two-way binding within their ecosystem. Our example would look something like this with AngularJS (untested code, see above):
But, be aware that some fully-fledged two-way binding extensions do exist for Backbone as well (in raw, subjective order of decreasing complexity): Epoxy, Stickit, ModelBinder…
One cool thing with Epoxy, for instance, is that it allows you to declare your bindings (model attributes view’s DOM element) either within the template (DOM), or within the view implementation (JavaScript). Some people strongly dislike adding «directives» to the DOM/template (such as the ng-* attributes required by AngularJS, or the data-bind attributes of Ember).
Taking Epoxy as an example, one can rework the raw Backbone application into something like this (…):
All in all, pretty much all «mainstream» JS frameworks support two-way binding. Some of them, such as Backbone, do require some extra work to make it work smoothly, but those are the same which do not enforce a specific way to do it, to begin with. So it is really about your state of mind.
Also, you may be interested in Flux, a different architecture for web applications promoting one-way binding through a circular pattern. It is based on the concept of fast, holistic re-rendering of UI components upon any data change to ensure cohesiveness and make it easier to reason about the code/dataflow. In the same trend, you might want to check the concept of MVI (Model-View-Intent), for instance Cycle.
Двустороннее связывание Angular, чуть больше понимания
От переводчика: два года назад я начал свой первый проект на Angular(2+), имея большой и успешный бэкграунд AngularJS. Переход потребовал заметного форматирования мышления, поскольку слишком много на A1 и A2+ делается «чуть-чуть по другому». Болезненность перехода мне заметно снизил блог thoughtram. Я ещё год назад получил разрешение перевести эту статью «об элементарном и всем легко понятном». Но руки они такие руки (своих статей пачка недописанных). Что удивительно, статья неплохо переводится гугл транслейтом. Но некоторые нюансы в этом переводе терялись, не говоря об авторском стиле. Авторский стиль не сохранился в полной мере и в моей версии. Но, надеюсь, настроение и мысли статьи мне удалось передать.
Я понимаю, что Angular не самая востребованная тема на Хабре, но надеюсь, что перевод поможет кому-то, так же как исходная статья помогла когда-то мне.
Вот что вызывало вау-эффект в старом добром AngularJS, так это «двустороннее связывание». Эта магия мгновенно влюбляла в AngularJS, и ломала все представления о скучном программировании страниц и (о, ужас!) веб-форм. Изменения в данных мгновенно отображались на экране и наоборот. Те, кто раньше разрабатывал приложения на jQuery, воспринимали связывание, как попадание в сказку. А бородатые монстры, пилившие толстых клиентов ещё до jQuery, начинали судорожно пересчитывать бездарно потерянные человеко-месяцы.
И, более того, магия двустороннего связывания была доступна не только для специальных нотаций и избранных компонентов. Мы могли легко её использовать в наших собственных директивах и компонентах (просто установив параметр конфигурации).
В Angular2+ создатели отказались от встроенной двусторонней привязки данных (кроме как через ngModel). Но это не означает, что мы не можем использовать двустороннее связывание в собственных директивах… Просто халява кончилась и теперь нужно кое-что делать самостоятельно. И, желательно, c пониманием того, как оно устроено в Angular.
Оглавление
Двустороннее связывание в двух словах
В A2+ только одна единственная директива реализует двустороннюю привязку данных: ngModel. И на первый взгляд, это та же магия, что и в AngularJS (только в другой нотации). Но что под капотом?
Как ни удивительно, под капотом всё относительно просто и логично: двустороннее связывание сводится к привязке свойств и привязке событий. Две односторонние привязки, вместо одной двусторонней? Хорошо, давайте две.
Да-да, это прекрасное и удивительное демо Angular2 от 2009 года. Без шуток, прекрасное. При изменении поля, значение username попадает в модель, и тут же отражается в приветствии на форме.
Но как это работает? Напомним, что двустороннее связывание в Angular2 это привязка свойств и привязка событий. И да, они могут быть одновременно доступны в одной директиве. Более того, даже без ngModel, мы легко могли бы реализовать двустороннее связывание данных. Например, так:
C выводом <
Мы связываем свойство username модели Angular со свойством value элемента ввода браузера (односторонне связывание из модели в представление).
Мы также привязываем к событию input нашего элемента выражение. Которое присваивает значение $event.target.value свойству username модели.
А что такое $event.target.value? Как уже упоминалось, $event полон различной небесполезной информации о событии. В данном случае это InputEventObject, в котором свойство target ссылается на элемент DOM, который иннициировал событие (т.е. наш элемент ввода).
Итак, всё, что по сути мы делаем – читаем содержимое (value) элемента ввода ($event.target), когда пользователь вводит значение. И когда мы присвоим это значение username, данные представления отправятся в модель.
Вот и всё. Это и есть «двустороннее связывание в двух словах». Красота?
Но когда же ngModel вступит в игру? Сценарий работы с элементами ввода очень распространен и востребован. И почему-то хочется иметь директиву, которая прячет реализацию и спасает от лишних нажатий клавиш.
Понимание ngModel
Если заглянуть в исходники, то можно убедиться, что ngModel также имеет привязку к свойству и событию. Вот как выглядит наш пример ngModel, но без использования сокращенного синтаксиса:
Практически всё то же самое. Привязка свойства [ngModel] заботится об обновлении значения элемента ввода. Привязка события (ngModelChange) уведомляет мир, что происходят изменения в DOM.
А вы заметили, что выражение-обработчик использует только $event, а не $event.target.value. Что-то тут не так? Отнюдь. Как сказано выше, $event это синтетическая переменная, которая несёт полезную нагрузку. Решение, что считать полезным берёт на себя Angular. Другими словами, ngModelChange берет на себя извлечение target.value из внутренней $event и просто отдаёт нам то, что мы и хотим, без упаковки и бубна. Если быть технически точными, эти занимается DefaultValueAccessor: это он занимается извлечением данных и переносом их в базовый объект DOM, хотя,… можно просто про это не думать).
И последнее, но не менее важное: поскольку писать username и ngModel дважды все-таки излишне, Angular допускает использование сокращенного синтаксиса [()], также называемого «банан в коробке». Что аналогично предыдущему примеру, и возвращает нас к примеру из начала раздела, но уже с пониманием реализации ngModel. Обеспечивающей то самое двустороннее связывание.
Создание собственных двусторонних привязок данных
Теперь мы знаем достаточно, чтобы создавать собственные двусторонние привязки данных. Все, что нужно сделать, просто следовать тем же правилам, что и ngModel, а именно:
Но, допустим, мы создаем пользовательский компонент счетчика (и не хотим использовать пользовательский элемент управления формы).
У нас есть свойство компонента counter для отображения текущего значения счетчика. Чтобы обеспечить ему двустороннюю привязку, первое, что нужно сделать, — это превратить его в Input параметр. Для этого очень кстати декоратор @Input():
Это уже позволяет привязать свойство компонента к потребителю следующим образом:
Теперь нам нужно задать @Output() событие с тем же именем (counter) и суффиксом Change (получается counterChange). Мы хотим возбуждать это событие при каждом изменении counter. Для чего добавим @Output() свойство. И добьём, в пару геттеру, сеттер counter, в котором мы будем перехватывать изменение значения и выкидывать событие с актуальным значением счётчика:
Это оно! Теперь мы можем привязать выражение к этому свойству, используя двусторонний синтаксис привязки данных:
Проверьте демо и попробуйте!
Заключение
Angular больше не поставляется со встроенной двусторонним связывание данных. Взамен «в коробке» есть API-интерфейсы, которые позволяют реализовать полное связывание как связывание свойств и событий.
ngModel поставляется как встроенная директива двустороннего связывания в FormsModule (не забудьте добавить его в секцию imports объявления @NgModule: прим. пер). Связывание через ngModel должно быть предпочтительным при создании компонентов, которые служат в качестве пользовательских элементов управления форм. В остальном всё зависит от ваше фантазии.
PS от переводчика: реализация связывания в A2+ стала более современной. Теперь для наблюдения за изменениями «по феншую» используется почти «бесплатные» сеттеры (хотя понятно, что механизмы для dirty-checking остались, как минимум для высокоуровневых пользовательских компонентов). Это позволило отказаться от 100500 watcher’ов (процедур следящих за изменениями «своих» данных). Которые в A1 любили создавать злобную нагрузку на браузер и требовали необычайно прямых рук при планировании насыщенных интерактивных страниц.
При правильно спроектированных компонентах, A2 «из коробки» стал значительно более «отзывчивым». Пусть и за счёт труда программистов. Теперь можно разместить легион компонентов на странице и не беспокоится за ресурсы процессора.
Обратной стороной медали стала начальная стоимость «процесса входа» в A2+, что повлияло на популярность фреймворка. Но и у A1 была высокая стоимость входа, только она была отнесена на высшую лигу. Из-за непонимания как организовывать большие приложения, многие прототипы «взлетевшие» на A1, потом «рассыпались» и переписывались на React и Vue.
Надеюсь, что этой статьёй я помогу немного снизить порог для начального входа в A2+, который продолжает оставаться востребованным (о чём я знаю не понаслышке).
Знаете ли вы js библиотеку с two way binding, хранящую все данные в html?
Оценить 2 комментария
1) На некоторых задачах, даже быстрее будет. Вот сравните заполнение списка на jquery, ko & ng: plnkr.co/edit/cTWetiNBorS8P2xcHhgm?p=preview
2) Во первых ещё один запрос. Во вторых повторная передача данных. В третьих дополнительный код на сервере.
3) Насколько я понимаю, вопрос изменения html внешними плагинами это не решает, только через ангуляр потом взаимодействовать.
1) я так и не понял как этот бенчмарк относится к задаче.
2) зато позволяет более гибко подойти к разработке. А код на сервере будет в любом случае, и дублирования я не вижу. Только «лишний» запрос на сервер. Возможно я просто не знаю деталей вашей задачи.
3) раскройте пожалуйста мысль, как вы себе это представляете?
Вообще у меня сложилось впечатление что я вас вообще не так понял. Пример «желаемого» инструмента был бы уместен.
Примера не знаю. Опишу задачу, в которой вижу этот инструмент уместным. Есть крупный сайт с серверным рендеренгом шаблонов. И кучей не звязанных jquery-плагинов. Исторически сложившийся говнокод. Они как-то там взаимодействуют на уровне html. Мне нужно несколько «оживить» существующие данные.
Эти данные уже есть на странице в виде html. Если добавить их на страницу ещё раз в виде json, размер страницы возрастёт в 2 раза, а она не маленькая и так. К тому же это требует изменений на сервере. И сами по себе данные не простые, с кучей фильтров, какстомных тегов в серверных шаблонах. Их даже в json сдампить не так просто.
В идеале мне хотелось бы с минимальными усилиями, не вникая во всё происходящее оживить часть html. То есть two way binding был бы очень кстати. Большой скорости мне не нужно, обновления будут не частые (типа увеличения числа лайков).
Таким образом ей можно было бы подсунуть, допустим хтмл сгенерённый на сервере.
Идея интересная, можно использовать ng-init или сделать аналог, сделал пример на angular light
До загрузки приложения отображает «Likes: 15», когда приложение загрузится, директива al-take-value возьмет значение элемента, когда с сервера будет прилетать информация, то текст будет обновляться с пом. директивы al-text, хотя для более сложных данных вариант с json + ng-init может быть удобнее.
Или другие библиотеки могли бы менять html, а эта библиотека работала бы с ним так же как если бы изменения сделаны были через неё.
Gaperton’s blog
Руководство по two-way data binding в React.
Во-первых, он таки в React есть. Называется value link. Это такой дизайн паттерн. И не смотря на то, что Фейсбук убирает свою кривую реализацию оного из React, он не может вам запретить его использовать.
Понимаем, что это такое, и учимся делать формы на React просто и приятно.
Во-вторых, этот паттерн способен на куда большее, чем примитивная его реализация в React. Например, он помогает весьма изящно делать валидацию ввода в реальном времени.
Учимся делать валидацию форм красиво, без традиционной для таких случаев боли:
Ну и в третьих. Самое интересное в этом паттерне вовсе не тот очевидный факт, что он устраняет ненужные коллбэки. А то, что он позволяет полностью отделить (и инкапсулировать) способ, которым выполняется data binding, как от слоя view, так и от слоя данных. Чего вообще ни один из популярных фреймворков не может.
Что, например, позволяет нам легко и непринужденно работать со сложным состоянием в React State, без какой-либо черной магии и кучи сторонних библиотек.
Учимся работать со сложным состоянием:
Comments:
Руководство по two-way data binding в React.
какие-то убойные ссылки вы подсунули, не просто браузер схлопывается а весь айпад перегружается 🙂
реакт я не видела, а ангулар меня не впечатлил, хотя мы взяли оттуда пару идей
но наш подход propriatery и вряд ли он когда выйдет за пределы компании
Edited at 2016-06-05 01:05 pm (UTC)
> видимо пора выходить на публику с нашим подходом
Выходите на здоровье, за чем дело встало. Публика оценит. Только в своих постах, пожалуйста.
Edited at 2016-06-05 05:09 pm (UTC)
мучайтесь дальше с вашим реактом 🙂
Нахуй пошел из моих комментов. Вместе со своими блевничками, фреймворками, самомнением, и оценочными суждениями. Вежливости и профессиональному любопытству он меня учить собрался, смотрите. 🙂
Edited at 2016-06-05 09:09 pm (UTC)
А нормальным людям я должен напомнить о политике комментариев здесь.
Edited at 2016-06-08 06:26 am (UTC)
Выглядит, конечно симпатично, но есть вопрос:
И в качестве оффтопика: пробовали ли вы mobx? Мы в текущий проект его засунули, и он вроде и работает, но реактивность местами создает неожиданные трудности (например, при копировании объектов реактивность теряется и нужно делать закат солнца вручную).
Можно вынести разделяемый стейт в состояние корневого компонента, и передавать вниз линки на его части (как в статье номер 3). Но в целом да. Большое приложение на одних линках собирать мягко говоря не очень удобно. Потребуется стейт-контейнер, который может побольше, чем raw react component state.
И он у нас есть. Вот он: https://github.com/Volicon/NestedTypes А вот его привязка к React, где он используется вместо React state. https://github.com/Volicon/NestedReact Линки в данном случае делаются на его элементы.
> И в качестве оффтопика: пробовали ли вы mobx?
Помимо определения глубоких изменений (которые у нас определяются по другому, и обладают транзакционной семантикой, в рамках которой наблюдатели могут добавлять к транзакции изменения, и это будет определено как одно изменение), NestedTypes:
— содержит механизм сериализации, который умеет, в частности, работать с рекурсивными данными вроде деревьев, и отношениями по id. https://github.com/Volicon/NestedTypes/blob/master/docs/RelationsGuide.md
— является «динамически типобезопасным». То есть, он при присваивании в аттрибуты приводит типы, так, что сломать протокол невозможно. Например, если аттрибут Boolean, то там будет всегда лежать Booolean. И на сервер всегда уйдет Boolean.
В наших продуктах (это здоровенный SPA на 100К строк кода) ни flux, ни React state вообще не используется. Только вот это, как единая техника работы с состоянием.
Edited at 2016-06-06 10:39 pm (UTC)
What is 2-way data binding?
While skimming through articles on Medium, I came across the term “two-way data binding”. I didn’t look it up then because I had to leave for office. While having lunch with my colleague the other day, he talked about it again and it seemed interesting. So after coming home, I went straight to my room fired up my PC and googled the term. I read a couple of articles and today I am gonna share what I learned with you guys.
What is two-way data binding?
After reading it for the first time it feels like it’s a complicated concept. But good news, it isn’t.
At first, it does seem like magic. But when you look underneath there are some problems with this approach. Angular v1 by Google used this approach and it didn’t turn out well. Angular apps became slower as they grew bigger and one of the reasons for the slowness was 2-way data binding.
So is 2 way data binding bad?
Not really. In my opinion, it’s a neat trick to handle data manipulation. A scenario where it works really well is in case of widgets which display editable data. Say you are rendering a grid and it displays data fetched from a model and the User just doesn’t want to look at it but manipulate data as well. Meaning, he/she can click on a cell, change the value and either by clicking on save or on focusout the data changes not just in the representation but also in the model. So next time when the grid renders it renders with the saved data. I think in this case 2-way data binding approach is justified.
But it isn’t that clean and simple, there is some housekeeping that you have to do to ensure you don’t end up with a sluggish UI. Whenever these widgets are destroyed, you need to take care of their events. They must be turned off or it may lead to memory leaks and you certainly don’t want that.
Also, you may run into problems where view A updates model B, model B updates view C and view C may again update a different model. So that’s something you need to keep in mind.
That’s it for today. Hope you found it helpful. Thanks for reading!