AngularJS popularized two‑way binding (not the first conceptually; ActionScript had analogs). Vue supports it; React does not by default but can emulate it. Using Angular as an example, here’s how it works, with pros/cons.
This is just a starting point for discussion.
[toc]
Usage
First, let’s look at how two-way binding is used. In the component below, we declare username and use [(ngModel)] for two-way binding, which allows real-time synchronization between the Model (M) and View (V). This is two-way binding.
<p>
form works!
<input [(ngModel)]="username"/>
</p>
<h3>
I am {{
username
}}
</h3>
The benefit of this is that the view layer and the form object remain consistent. You directly get the data value of the form object, and type issues are handled in advance, no need for manual operations.
Implementation
It’s easy to use, now let’s see how Angular implements it.
[(ngModel)] is syntax sugar
You should know that [(ngModel)] is syntactic sugar. It can be broken down into <input [ngModel]="username" (ngModelChange)="username=$event"/>. ngModel is a directive, and ngModelChange is a method exposed by the directive. So we need to focus on the implementation logic of the ngModel directive.
data => view uses [] view => data uses ()
[ngModel]
When we modify the value of
namein the data, the value in the view changes in real time.
Two questions:
- How does Angular know when an object changes?
Dirty checking - How does Angular update the value to the final native DOM element?
Modify the element's value property
Dirty checking
The full name of dirty checking is dirty data checking, which refers to data that has changed.
In Angular, asynchronous operations like Click or XHR automatically trigger change detection, find these changes, and then perform update actions.
However, I understand that these changes are not immediately updated as soon as they occur. Instead, updates are done in batches after the model stabilizes.
Note: Angular does not use timed polling for checking.
Update the view
Find the nativeElement through the directive and modify the value property.
// packages/forms/src/directives/ng_model.ts
constructor(
@Optional() @Host() parent: ControlContainer,
@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators:
Array<AsyncValidator|AsyncValidatorFn>,
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
super();
this._parent = parent;
this._rawValidators = validators || [];
this._rawAsyncValidators = asyncValidators || [];
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
// packages/forms/src/directives/default_value_accessor.ts
writeValue(value: any): void {
const normalizedValue = value == null ? '' : value;
this._renderer.setProperty(this._elementRef.nativeElement, 'value', normalizedValue);
}
// packages/platform-browser/src/dom/dom_renderer.ts
setProperty(el: any, name: string, value: any): void {
NG_DEV_MODE && checkNoSyntheticProp(name, 'property');
el[name] = value;
}
(ngModelChange)
Detect changes in the form input value and update the data object.
// packages/forms/src/directives/ng_model.ts
ngOnChanges(changes: SimpleChanges) {
this._checkForErrors();
if (!this._registered) this._setUpControl();
if ('isDisabled' in changes) {
this._updateDisabled(changes);
}
if (isPropertyUpdated(changes, this.viewModel)) {
this._updateValue(this.model);
this.viewModel = this.model;
}
}
// packages/elements/src/component-factory-strategy.ts
/** Runs change detection on the component. */
protected detectChanges(): void {
if (this.componentRef === null) {
return;
}
this.callNgOnChanges(this.componentRef);
this.componentRef.changeDetectorRef.detectChanges();
}
Now that we roughly understand the principle, how to implement two-way binding manually without a framework?
Alternatives
Object.defineProperty
- Supported in IE9
- Principle behind Vue v2 two-way binding
const obj = {};
Object.defineProperty(obj, 'value', {
get: function () {
console.log('Get value', value);
return val;
},
set: function (value) {
console.log('Set value', value);
},
});
Proxy
- Not supported in IE
- Proxy is an ES6 feature
- Principle behind Vue v3 two-way binding
const obj = {};
const handler = {
get: function (target, prop) {
console.log('Get value', target[prop]);
return target[prop];
},
set: function (target, prop, value) {
console.log('Set value', value);
target[prop] = value;
},
};
const proxy = new Proxy(obj, handler);
```
## References
- [Angular 2 Change Detection - 2](https://segmentfault.com/a/1190000008754052)
- [angular dirty checking principle and pseudo-code implementation](https://juejin.im/post/5b193353f265da6e0d7a2dcd)

