Angular's [(ngModel)], Vue's v-model, and Svelte's bind:value all sell the same trick: a form field and a piece of state stay glued together. Type in the box and the model updates; change the model in code and the box updates. It looks like magic, but it's two one-way bindings wired back to back — a DOM event going one way, a property setter going the other.
Implement twoWayBinding(input, model, key). Sync input.value with model[key] in both directions, and return an unbind() that tears the binding down.
function twoWayBinding(input, model, key) {
// returns unbind(): removes the listener and restores model[key] as plain data
}
const input = document.createElement('input');
const model = { name: 'ada' };
const unbind = twoWayBinding(input, model, 'name');
input.value; // 'ada' (initial sync)
input.value = 'grace';
input.dispatchEvent(new Event('input'));
model.name; // 'grace' (DOM -> model)
model.name = 'lin';
input.value; // 'lin' (model -> DOM)
unbind(); // stop syncing
input event and copy input.value into the backing store.model[key] as an accessor (Object.defineProperty get/set) whose setter writes to input.value.Object.keys(model) and behave like a normal property to readers.unbind() removes the event listener and turns model[key] back into a plain data property holding the last value.