One-way data binding in vanilla JS (POC)
Learn how to implement one-way data binding in pure JavaScript without any frameworks
JavaScript frameworks like Angular, React, and Vue all provide built-in ways to bind data to the UI. But how do they work under the hood? I wanted to understand this better, so I built a simple one-way data binding implementation in vanilla JavaScript.
What is one-way data binding?
One-way data binding means changes in the application state are automatically reflected in the UI, but not the other way around. The flow is:
Model → View
When the model (data) changes, the view (UI) updates automatically.
The implementation
Here’s a simple implementation of one-way data binding in vanilla JavaScript:
// Our model - a simple object with some data
const model = {
name: 'John',
age: 30
};
// Proxy to intercept property access and modifications
const handler = {
set(target, property, value) {
target[property] = value;
// Update all elements that use this property
render();
return true;
}
};
// Create a reactive proxy for our model
const reactiveModel = new Proxy(model, handler);
// Render function to update the DOM
function render() {
// Find all elements with data-bind attribute
document.querySelectorAll('[data-bind]').forEach(element => {
const property = element.getAttribute('data-bind');
if (property in reactiveModel) {
element.textContent = reactiveModel[property];
}
});
}
// Initial render
render();
// Example usage:
// <div data-bind="name"></div>
// <div data-bind="age"></div>
How it works:
- We create a model object with our data
- We use JavaScript’s Proxy to intercept property changes
- When a property changes, we call the render function
- The render function finds all elements with a data-bind attribute and updates them
Demo
Let’s see it in action:
<!DOCTYPE html>
<html>
<head>
<title>One-way data binding in vanilla JS</title>
</head>
<body>
<h1>Hello, <span data-bind="name"></span>!</h1>
<p>You are <span data-bind="age"></span> years old.</p>
<button id="updateName">Change Name</button>
<button id="updateAge">Change Age</button>
<script>
const model = { name: 'John', age: 30 };
const reactiveModel = new Proxy(model, {
set(target, property, value) {
target[property] = value;
render();
return true;
}
});
function render() {
document.querySelectorAll('[data-bind]').forEach(element => {
const property = element.getAttribute('data-bind');
if (property in reactiveModel) {
element.textContent = reactiveModel[property];
}
});
}
// Initial render
render();
// Event listeners for buttons
document.getElementById('updateName').addEventListener('click', () => {
reactiveModel.name = 'Jane';
});
document.getElementById('updateAge').addEventListener('click', () => {
reactiveModel.age = reactiveModel.age + 1;
});
</script>
</body>
</html>
Conclusion
While this is a simplified example, it demonstrates the core concept behind one-way data binding. Modern frameworks build on these ideas with more features and optimizations, like Virtual DOM for efficient updates and change detection algorithms.
In my next post, I’ll explore how to implement two-way data binding, where changes in the UI update the model as well.
You can find the complete code example on my GitHub.