
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).
Check out the demo below for a quick example:
DEMO
Let's break it down
What do we need to have two-way data binding?
- Aview, in our example HTML.
- Astate, kept in memory with JavaScript.
The Key feature is:
Every time thestate changes, theview 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 HTMLview:
<divclass="field"><labelfor="name">Enter your name:</label><inputid="name"type="text"name="name"data-model="name"/></div><divclass="field"><labelfor="title">Enter your title:</label><inputid="title"type="text"name="title"data-model="title"/></div><divclass="results"><h1data-binding="name"></h1><h2data-binding="title"></h2></div>
and astate:
conststate={name:'Francesco',title:'Front-end Developer'};
We can easily set theview the first time:
document.querySelector('[data-binding="name"]').innerHTML=state.namedocument.querySelector('[data-binding="title"]').innerHTML=state.titledocument.querySelector('[data-model="name"]').value=state.namedocument.querySelector('[data-model="title"]').value=state.title
But we want some magic, so that when we update the state:
state.name='Richard'state.title='Technical Lead'
theview should update too.
To achieve this, we could modify the default behaviour of theset
property for thestate
object, so that other than update thestate, it would also update ourview.
One way to do that in JavaScript is using theProxy Object:
constcreateState=(state)=>{returnnewProxy(state,{set(target,property,value){target[property]=value;// default set behaviourrender();// updates the view every time the state changesreturntrue;}});};conststate=createState({name='Francesco'title='Front-end Engineer'});
With the power of the Proxy every time we update ourstate
, therender
function will be called.
A possible implementation ofrender
can be:
constrender=()=>{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;};
We just miss the last little piece. Every time we modify theview, thestate should change accordingly. We can obtain that adding an event listener to the inputs: 😎
constlistener=(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);
AndVoilá! Now the trick is complete! 👨💻
More generic implementation (POC) 🌟
Top comments(8)

How extend it to include array of elements, like
I have in DOM:
< input name=product[1][quantity]>
and so on...

- LocationZürich (Switzerland)
- EducationMaster of Science in Software Engineering
- PronounsHe/Him
- WorkFront-end Engineer
- Joined
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)

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?

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);

- LocationDelhi, India
- WorkFullstack Web Developer at Codeword Tech
- Joined
Helpful article, thanks 😋
For further actions, you may consider blocking this person and/orreporting abuse