Two-way data binding is a pattern which many developers either love or hate. When asked, many developers choose Angular 1 or 2 because of two-way data binding. However, many other developers choose to use React (or something else) specifically because it does not require two-way data binding.
So, why do some developers avoid two-way data binding, while others love it? How does two-way data binding factor into the component-driven development revolution?
What is Data Binding?
Data binding is the mechanism that connects a data model to the user interface (UI). While any kind of code can be used to data bind, for the purposes of this posting, data binding refers to a library or framework that provides data binding services through an API. This does not require the developer to manually retrieve model values, nor perform some kind of DOM or other template string manipulations to populate the UI with model data. Data binding services are very popular with developers because they greatly simplify the process of updating the UI and reduce the amount of boiler plate code in applications.
The data model, the user interface, and the binding mechanism can take many forms, but ultimately the goal is to connect the user interface with some kind of model. There are three primary forms of data binding: one-time, one-way, and two-way. Choosing one over the others involves many factors.
What Is One-Time Data Binding?
One-time data binding occurs one time between the model and the UI. Typically, when the UI is initially created, the values of the model data at that particular moment in time are used to populate the newly created UI. The binding occurs once and there is no automated mechanism to update the UI when future changes to the model occur. Commonly used one-time binding libraries include Underscore templates and Handlebars templates. Also, Angular 1 supports one-time bindings within templates as well.
From a performance perspective, one-time is the highest performance because it happens once; therefore, it does not require any change detection mechanisms to detect changes to the underlying model or in the UI. Change detection mechanisms introduce potential performance problems into a web page because blocking code must be executed to determine if model changes need to be propagated to the UI.
One-time data binding is practical when the data never or very rarely changes. When data changes regularly or very frequently, one-time data binding becomes a hindrance to simple and efficient UI updates. To solve the problem of UI updates for regularly changing model data, one-way (or two-way) data binding is used.
What Is One-Way Data Binding?
One-way data binding creates an ongoing connection between the model and the UI. Changes to the model are reflected in the UI through some kind of process controlled by a library or framework. One-way data binding only propagates changes from the model to the UI, not vice versa. Central to the implementation of one-way (and two-way) data binding is an automated mechanism to determine if changes to the model have occurred. This process is called change detection.
What Is Change Detection?
From the perspective of one-way data binding, change detection involves two aspects: knowing when the model has changed, and knowing what changed in the UI as a result of the model changes.
To detect model changes, there are three fundamental patterns that are available: manual, publisher subscriber, and asynchronous wrappers. Manual change detection can occur in two forms – explicit and implicit. Explicit manual change detection is when the developer must tell the library or framework explicitly that a change has occurred (or potentially occurred) and update the UI with the new model data, as appropriate.
Manual Change Detection
Two examples of manual model change detection are React, and creating custom directives in Angular 1 that have their own custom events. In both cases, the developer’s code must notify the library or framework that a model change has occurred. With React this is accomplished via the setState function and with Angular 1 this is accomplished via the $digest or $apply functions.
The implicit manual change detection pattern is when the library or framework executes the developer’s code, and upon completion, executes its own change detection mechanism. Angular 1 uses this pattern for its directives or other third party libraries. Often this approach appears magical to the developer, and it’s possible for model changes to be missed if the developer is not familiar with how the magic works behind the scenes when incorporating custom functionality (such as custom events in custom directives).
An example of the magic is registering an event with the ngClick directive. Once the registered click function is executed by Angular 1, the $apply function is called behind the scenes to update the UI.
Publisher Subscriber Change Detection
The publisher subscriber change detection pattern requires the library or framework to be notified the moment model data has changed. Unlike the manual pattern where a change occurs and then the system is notified as two distinct steps, the publisher subscriber pattern allows the developer to make the change. And as part of the change itself, the change detection mechanism is triggered.
This change detection approach requires the use of function properties to change model values (or ES5 object property setter functions). Functions are required so both the task of setting a value and executing code can be accomplished with a single line of code from the developer’s perspective. An example of this pattern is the KnockoutJS library.
Asynchronous Wrappers Change Detection
The final approach of asynchronous wrappers uses the concept of zones to intercept all asynchronous operations, and then perform change detection when the asynchronous operations are complete. On the surface it sounds similar to implicit manual change detection, but its radically different because the zones capture all asynchronous operations. This is where all model changes originate, therefore, no special knowledge is needed by the developer to use the system; it just works.
What are Zones and Microtasks?
Zones solve several problems (such a managing multiple asynchronous operations within a single context), but for the purposes of change detection, they provide a mechanism known as microtasks. Once complete, microtasks allow a UI framework/library to be notified that change detection should be triggered. Currently, Angular 2 is the major framework that uses zones, and once the microtask queue of a zone has been drained, the tick function in Angular 2 is fired, triggering change detection to occur.
Updating the UI
Once a model change has been detected, then the UI needs to be updated and the second phase of change detection occurs. With most one-time data binding, and even many one-way data binding systems, the DOM is updated inefficiently to reflect the model changes. Typically, the inefficiency presents itself as destroying the branch of the DOM tree being updated and completely recreating and rendering it again. Generally, this is not the most efficient way to update the UI, but it is the easiest way for a developer to code the UI update process.
Both KnockoutJS and Angular 1 do not provide an overall efficient mechanism for performing these DOM updates. Within their code bases certain areas are extremely efficient. However, this efficiency does not always extend to the whole library/framework, much less the custom UI code provided by developers.
React and Angular 2 take a different approach. While React requires the developer to manually notify React of model changes, once React takes over, it makes UI changes through a process called reconciliation. Whereas Angular 1 focuses on change detection on the model, React focuses on change detection in the UI, looking for what actual changes to the UI resulted from the model change, and based upon the UI changes, it efficiently updates the UI to reflect those changes.
What Is Two-Way Data Binding?
With a fuller understanding of one-time and one-way data binding, let’s explore two-way data binding. One-time and one-way data bindings flow from the model to the UI. Two-way includes one-way, but it also facilitates the flow of data from the UI to the model. Whether a library or framework explicitly supports two-way data binding, the only means by which to retrieve new data from the UI is through UI events such as key up, input or click.
Updating the model from the UI is implemented using one of two paths: UI element to UI event to model or UI event to model to UI element. Typically, the former describes two-way data binding, while the latter is a form of one-way data binding. The difference is how the UI element is updated. Does the UI element have two sources of data or one source? When the UI element is updated from the model and user input, it has two sources of data. When the UI element is always updated only from the model, it has one source of data. The one source of data results from UI events triggered by user interaction which update the model first, and then the model updates the UI element. The element itself is not directly updated by the triggered event.
Even though both React and Angular 2 update the UI element from user interaction, React forces the model to be updated first, and then the UI element is updated. In contrast Angular 2 (from the developer’s perspective) updates the UI element and then triggers the update of the model. This is why people consider React to be one-way binding, and Angular 2 to use two-way data binding (Angular 1 works the same way as Angular 2 in this regard as well).
One-Way versus Two-Way: The Academic Argument
On a conceptual level, two sources of data sounds problematic and generally it is. So the academic argument of two-way data binding typically falls in favor of opposing it in favor of one-way data binding. Unfortunately, the argument is not that simple. The context or scope of the data binding must be considered as well. When considering the flow of data and data binding, the question must be asked: how is the data flowing and what is being bound?
UI data changes can occur within two scopes within a single component or between components. Using two-way data binding between components results in components receiving data from multiple sources, and this can be problematic. Using two-way data binding strictly within a component is not nearly as problematic.
Take typing into an input field: the updating of the input field occurs locally within a component; it does not affect other components directly. So the argument against two-way data binding does not apply in this situation. The two-way data bind is trivial and controlled completely within the context of one component.
Understanding the scope of the data binding is essential to understanding the benefits and risks of each approach, not just assessing it based upon two sources of truth.
One-Way versus Two-Way: The Practical Reality
Using two-way data binding between components can results in components transitioning into undesired states because of conflicting data being propagated from multiple sources. Therefore, one-way data binding is preferred even though it will require a more complicated data flow and more coding on the part of the developer.
Avoiding two-way data binding within a component, and implementing the structures needed for one-way data binding for trivial operations such as entering text into an input element, adds unneeded complexity with almost no practical gain. React suffers from this problem of requiring the creation of event handlers, updating of state, and re-rendering of the user interface to allow the simple act of typing text into an input element.
This is where Angular 2 shines: it encourages the use of two-way data binding techniques within a component for trivial model-UI updates, while requiring more one-way data binding interactions between components. This is the best of both worlds.
Data binding is an important tool used by developers to populate user interfaces with model data. There are many ways to bind data, each with their own benefits and costs. Important factors include performance, data flow, and code complexity. There are many frameworks and libraries that provide various approaches to the data binding, including Handlebars, KnockoutJS, Angular 1 & 2, and React.
Each of these frameworks and libraries are very opinionated about how to bind data, including how model changes are detected and data is propagated. Handlebars (and similar template systems) are great for one-time data binding, React is great for one-way data binding, and Angular 2 provides the right mix of data binding options through one-way for components and two-way within components. Regardless of the method used, the key is to understand the concepts and ideas behind data binding, and how the library you choose approaches this important and complex topic.
Author: Eric Greene, one of Accelebrate’s instructor