DEV Community

Cover image for Ever wondered ๐Ÿค” how data binding works in modern UI frameworks & libraries ?
Jay Prakash
Jay Prakash

Posted on

Ever wondered ๐Ÿค” how data binding works in modern UI frameworks & libraries ?

We can implement our own data binder which will sync view with model and vice versa. With the release of ES6 Proxy object, it's now more simpler to implement our own data binding functionality.

2-way data binding demo

Before implementing our own, let's have a brief introduction of Proxy object.

Proxy object gives us the functionality to wrap an original object & then we can access the properties of original object through that Proxy object.

The advantage of using Proxy object is that we can easily attach a handler to the Proxy object so that we can intercept in between if someone tries to access that Proxy object because control will go to handler first before going to original object. Let's see in below example, how it works:

const originalObj = {
    firstName: "Jay",
    lastName: "Prakash"
};
const handler = {
    set: (target, property, value) => {
        if (value.length > 2) {
            target[property] = value;
        } else {
            alert("First Name should be more than 2 characters !");
        }
        return true;
    },
};
const proxyObj = new Proxy(originalObj, handler);
proxyObj.firstName = 'Jo';
Enter fullscreen mode Exit fullscreen mode

In above code snippet, what I'm doing is simply defining an object called originalObject.

Then, I'm defining another object called handler that contains a trap called set. We call this trap because this is what it's doing, it is trapping or intercepting the call to original object.

The set trap will get called when someone tries to set/update the property of originalObject, in our case, whenever we try to update the firstName property with value of length 2, it will give an alert message because in our set trap we are checking if value that we intend to set has length greater than 2 otherwise we are triggering an alert and simply returning from set trap.

Please note, there are many traps for Proxy object, here we are only using set trap, for a full list of traps, refer here.

Now, we know that how JavaScript Proxy object works, so let's start implementing our own two-way data binder with the help of JS proxies.

So, to bind data between model and view, we need to define some nomenclature. We will use HTML5 data attribute to accomplish this task. Let's say we define data-model attribute for defining model and data-bind attribute for defining on which HTML elements, the respective data will be binded. One thing to note here is that values of these data attributes will act like keys to recognize which element has to be updated when it's binded model changes it's state. Let's see in below example what I'm talking about:

<input type="text" data-model="input_1" />
<div data-bind="input_1"></div>
Enter fullscreen mode Exit fullscreen mode

In above example, you can see that I have given two attributes data-model & data-bind and values of both attributes are same that means INPUT element will bind to DIV element, so what we will do is whenever value of INPUT element containing data-model changes, we will update the respective element with data-bind attribute matching the same key, in our case, it is input_1.

Let's create a method to update view.

 // Update view when current state of object changes
 const updateView = (obj, prop) => {
     // Get all elements which is binded to the obj prop that got changed
     const elements = document.querySelectorAll(`[data-bind=${prop}]`);

     // Loop through all elements & sync view with the model
     elements.forEach(element => {
         const nodeType = element.nodeName;
         if (nodeType === 'INPUT') {
             element.value = obj[prop];
         } else {
             element.textContent = obj[prop];
         }
     });
 };
Enter fullscreen mode Exit fullscreen mode

In above code, we defined one method called updateView that is receiving two prameters: targetObject & the property that needs update. What we are doing is we are getting all elements of HTML page that contains data-bind attribute with the same key that we are receiving as second parameter. Then, we are looping through all such elements that needs an update and we update the value based on the key that we received as second parameter in this method.

Now, let's write some code to initialize a model and attach handlers to it.

// Define a object to hold the state
const obj = {}; // can be referred as a model to hold all data

// Get all elements which value needs to be updated back to model
const models = document.querySelectorAll('[data-model]');
models.forEach(model => {
    const attributeValue = model.getAttribute('data-model');
    obj[attributeValue] = '';
    // Add event listener on view elements for syncing back to model
    model.addEventListener('input', (evt) => {
        jsBinder[evt.target.dataset.model] = evt.target.value;
    });
});

// Create a proxy & attach handler to it
return new Proxy(obj, {
    set: (target, property, value) => {
        target[property] = value;
        // Sync View With Model
        updateView(target, property);
        return true;
    },
});
Enter fullscreen mode Exit fullscreen mode

Now, what we are doing is we are defining a variable called obj that will act as a model that will hold all model related data based on key.

Then, we are getting all elements from HTML page containing data-model attribute & attaching input event listener to each of those elements and in that listener what we are doing is we are trying to set property through Proxy object based on the key that we are getting from evt.target.dataset.model which in turn will invoke the set trap that is defined inside Proxy object. Now, in set trap, what we are doing is simply setting/updating the property based on whether it is new/old property and finally we are calling the updateView method that we created earlier to update the view.

Hence, in input event listener, we are syncing our model from view whenever our view changes and in set trap we are syncing back the view from model by calling updateView method if someone changes the model itself either programmatically or via some external source.

Huh, that was a lot of code, aggregating all lines of code above, let's see the following code that is whole implementation of our own binder. You can also find whole implementation in this GitHub repository.

// Initialize the model that will hold the object state
const jsBinder = (() => {

    // Update view when current state of object changes
    const updateView = (obj, prop) => {
        // Get all elements which is binded to the obj prop that got changed
        const elements = document.querySelectorAll(`[data-bind=${prop}]`);
        // Loop through all elements & sync view with the model
        elements.forEach(element => {
            const nodeType = element.nodeName;
            if (nodeType === 'INPUT') {
                element.value = obj[prop];
            } else {
                element.textContent = obj[prop];
            }
        });
    };

    // Define a object to hold the state
    const obj = {};

    // Get all elements which value needs to be updated back to model
    const models = document.querySelectorAll('[data-model]');
    models.forEach(model => {
        const attributeValue = model.getAttribute('data-model');
        obj[attributeValue] = '';
        // Add event listener on view elements for syncing back to model
        model.addEventListener('input', (evt) => {
            jsBinder[evt.target.dataset.model] = evt.target.value;
        });
    });

    // Create a proxy & attach handler to it
    return new Proxy(obj, {
        set: (target, property, value) => {
            target[property] = value;

            // Sync View With Model
            updateView(target, property);
            return true;
        },
    });
})();
Enter fullscreen mode Exit fullscreen mode

Final note, please share with your friends if this looks interesting to you, found any mistake, have some suggestions, questions etc, please post a comment I will try my best to answer it.

You can also find this same article published on linkedin here.

Thanks for reading & have a good day ahead.

Top comments (2)

Collapse
 
biomathcode profile image
Pratik sharma

Thanks, this was really helpful. Can you also explain why solid js is more reactive compared with other modern frameworks ? How does react achieve data binding given the virtual dom, that it creates, react is mostly rerendered UI rather than reactive.. ?

Collapse
 
jp024556 profile image
Jay Prakash

Hi Pratik, I don't know about Solid JS but in React, it is rather 1-way data binding and virtual DOM has nothing to do with data binding except when state changes, it helps react in re-rendering that component efficiently and hence new data gets re-rendered on the UI, so in a way, you can say React heavily depends on state to give the 1-way binding effect but to it's core, it's not doing any binding at all, it just re-renders the component on state change, you can also get 2-way binding effect in React, just you have to play with some event listeners.

Just remember one thing, every data that needs to be changed(or binded) should come through state otherwise component will not re-render and you will not get effect of data binding whether it is 1-way or 2-way.