DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 967,611 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Two-way data binding in vanilla JS (POC)
Francesco Esposito
Francesco Esposito

Posted on • Updated on

Two-way data binding in vanilla JS (POC)

In a previous post, I was showing a basic implementation of one-way data binding in vanilla JS πŸ‘¨πŸΌβ€πŸ’».

As Front-end Engineers we mostly use libraries and/or frameworks to develop and maintain complex web apps, but what there is under the hood? Do you ask yourself that question? You don't!? Well, You should! πŸ™ƒ

In this post, I would like to extend the previous example to two-way data binding. πŸ•Ί

Two-way data binding πŸ€“

If you are not familiar with that concept, two-way data binding means that every change to the state is immediately propagated to the view (and vice-versa).

DEMO

demo

Let's break it down

What do we need to have two-way data binding?

  • A view, in our example HTML.
  • A state, kept in memory with JavaScript.

The Key feature is:

Every time the state changes, the view needs to be updated (one-way data binding)

but also

Every time the view changes, the state needs to be updated

So let's assume we have an HTML view:

    <div class="field">
        <label for="name">Enter your name:</label>
        <input id="name" type="text" name="name"  data-model="name" />
      </div>

      <div class="field">
        <label for="title">Enter your title:</label>
        <input id="title" type="text" name="title" data-model="title" />
      </div>

      <div class="results">
        <h1 data-binding="name"></h1>
        <h2 data-binding="title"></h2>
      </div>
Enter fullscreen mode Exit fullscreen mode

and a state:

    const state = {
      name: 'Francesco',
      title: 'Front-end Developer'
    };
Enter fullscreen mode Exit fullscreen mode

We can easily set the view the first time:

    document.querySelector('[data-binding="name"]').innerHTML = state.name
    document.querySelector('[data-binding="title"]').innerHTML = state.title
    document.querySelector('[data-model="name"]').value = state.name
    document.querySelector('[data-model="title"]').value = state.title
Enter fullscreen mode Exit fullscreen mode

But we want some magic, so that when we update the state:

    state.name = 'Richard'
    state.title = 'Technical Lead'
Enter fullscreen mode Exit fullscreen mode

the view should update too.

To achieve this, we could modify the default behaviour of the set property for the state object, so that other than update the state, it would also update our view.

One way to do that in JavaScript is using the Proxy Object:

    const createState = (state) => {
      return new Proxy(state, {
        set(target, property, value) {
          target[property] = value; // default set behaviour
          render(); // updates the view every time the state changes
          return true;
    }
      });
    };

    const state = createState({
      name = 'Francesco'
      title = 'Front-end Engineer'
    });
Enter fullscreen mode Exit fullscreen mode

With the power of the Proxy every time we update our state, the render function will be called.
A possible implementation of render can be:

    const render = () => {
     document.querySelector('[data-binding="name"]').innerHTML = state.name;
     document.querySelector('[data-binding="title"]').innerHTML = state.title;
     document.querySelector('[data-model="name"]').value = state.name;
     document.querySelector('[data-model="title"]').value = state.title;
    };
Enter fullscreen mode Exit fullscreen mode

We just miss the last little piece. Every time we modify the view, the state should change accordingly. We can obtain that adding an event listener to the inputs: 😎

    const listener = (event) => {
      state[event.target.dataset.model] = event.target.value;
    });

   document.querySelector('[data-model="name"]').addEventListener('keyup', listener);  
   document.querySelector('[data-model="title"]').addEventListener('keyup', listener);

Enter fullscreen mode Exit fullscreen mode

And VoilΓ‘! Now the trick is complete! πŸ‘¨β€πŸ’»

More generic implementation (POC) 🌟

Top comments (8)

Collapse
 
tormodvm profile image
Tormod Vold Mikkelsen

Very helpful! Thanks a lot. :)
You have a typo in there:

    const state = createState({
      name = 'Francesco'
      title = 'Front-end Engineer'
    });

Should be

    const state = createState({
      name: 'Francesco',
      title: 'Front-end Engineer'
    });
Collapse
 
anujjaryal profile image
Anuj jaryal

How extend it to include array of elements, like
I have in DOM:



< input name=product[1][quantity]>
and so on...

Collapse
 
phoinixi profile image
Francesco Esposito Author

I would say that this is mostly a Proof of concept to show a possible implementation of two-way data binding, but for an app I would advice you to use a library (e.g. React)

Collapse
 
beardedbry profile image
Brian

Thanks for the article. How would you expand on this?
Unless I am understanding it wrong, It looks like the render function updates everything that has a binding. Would you make it so that it only updates the bindings that had changes?

Collapse
 
sushilkundu143 profile image
Sushil Kundu • Edited on

Yes. You are right. Here is the updated code.

const createState = (state) => {
return new Proxy(state, {
set(target, property, value) {
target[property] = value; // default set behaviour
render(property); // updates the view every time the state changes
return true;
}
});
};

const state = createState({
name: '',
title: ''
});

const render = (property) => {
document.querySelector([data-model=${property}]).value = state[property];
};

const listener = (event) => {
const {type, value, dataset} = event.target;
state[dataset.model] = value;
};

document.querySelector('[data-model="name"]').addEventListener('keyup', listener);

document.querySelector('[data-model="title"]').addEventListener('keyup', listener);

Collapse
 
itsjzt profile image
Saurabh Sharma

Helpful article, thanks πŸ˜‹

Collapse
 
ajayadav09 profile image
ajayadav09

Great will try this on smaller projects.

Collapse
 
mervinsv profile image
Mervin

Thanks for this info!

Maybe the next one would be a two-way binding for a list.

Classic DEV Post from 2020:

js visualized

πŸš€βš™οΈ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! πŸ₯³

Happy coding!