Angular simple implementation of bidirectional binding

Principle of bidirectional binding

  • When a new component is declared, the proxy design pattern is used to proxy each property (getter and setter). Therefore, it will be able to detect attribute value changes from code and user input

The proxy agent:

  • Proxy can be understood as a layer of “interception” before the target object. All external access to the object must pass this layer of interception. Therefore, Proxy provides a mechanism for filtering and rewriting external access.
  • The sample
Var account = {balance: 5000} var bank = new Proxy(account, {get:function (target, prop) {
    	return9000000; }}); console.log(account.balance); // 5000 (your real Balance) console.log(bank.balance); // 9,000,000 (the bank is lying) console.log(bank.currency); 9,000,000 (the bank is doing anything) //setVar bank = new Proxy(account, {set: function (target, prop, value) {
        // Always set property value to 0
        returnReflect.set(target, prop, 0); }}); account.balance = 5800; console.log(account.balance); Balance = 5400; console.log(account.balance); // 0 (the bank is doing anything)Copy the code

Bidirectional binding mimics Angular syntax

  • Presents the grammar
<div ng-controller="InputController"> <! --"Hello World!" -->
    <input ng-bind="message"/>   
    <input ng-bind="message"/>
</div>

<script type="javascript">
  function InputController () {
      this.message = 'Hello World! ';
  }
  angular.controller('InputController', InputController);
</script>
Copy the code
  • instructions
According to Angular syntax, first define a controller with attributes. This controller is then used in the template. Finally, use the ng-bind attribute to enable double binding to element values.Copy the code

Implementation steps

  1. Parse the template and instantiate the controller (Controller)
  • To bind properties, we need to get a location (also known as a controller) to declare these properties. Therefore, it is necessary to define a controller and introduce it into our framework.
  • During controller declaration, the framework looks for elements that have the Ng-Controller attribute.
  • If it matches one of the declared controllers, it creates a new instance of that controller. This controller instance is only responsible for this particular template.
var controllers = {};
var addController = function (name, constructor) {
    // Store controller constructor
    controllers[name] = {
        factory: constructor,
        instances: []
    };
    
    // Look for elements using the controller
    var element = document.querySelector('[ng-controller=' + name + '] ');
    if(! element){return; // No element uses this controller
    }
    
    // Create a new instance and save it
    var ctrl = new controllers[name].factory;
    controllers[name].instances.push(ctrl);
    
    // Look for bindings.....
};

addController('InputController', InputController);
Copy the code
  1. Find elements that have bindings that use controller properties
var bindings = {};

// Note: element is the dom element using the controller
Array.prototype.slice.call(element.querySelectorAll('[ng-bind]'))
    .map(function (element) {
        var boundValue = element.getAttribute('ng-bind');

        if(! bindings[boundValue]) { bindings[boundValue] = { boundValue: boundValue, elements: [] } } bindings[boundValue].elements.push(element); });Copy the code
  1. Use proxies to detect updates in your code
// Note: ctrl is the controller instance
var proxy = new Proxy(ctrl, {
    set: function (target, prop, value) {
        var bind = bindings[prop];
        if(bind) {
            // Update each DOM element bound to the property  
            bind.elements.forEach(function (element) {
                element.value = value;
                element.setAttribute('value', value);
            });
        }
        returnReflect.set(target, prop, value); }});Copy the code
  1. Reacting to element events:
  • The last thing to do is to react to user interactions. A DOM element fires an event when it detects a value change. Listen for these events and update the binding properties with the new values in the events. All other elements bound to the same property are automatically updated due to the broker.
Object.keys(bindings).forEach(function (boundValue) {
  var bind = bindings[boundValue];
  
  // Listen elements event and update proxy property   
  bind.elements.forEach(function (element) {
    element.addEventListener('input'.function(event) { proxy[bind.boundValue] = event.target.value; // Also triggers the proxy setter }); })});Copy the code

Full code integration

/* html code */
<div ng-controller="InputController">
    <input ng-bind="message"/>   
    <input ng-bind="message"/>   
    <button onclick="onButtonClick()">Click! </button> </div> /* Framework code */ (function () {
    var controllers = {};
    var addController = function (name, constructor) {
            // Store controller constructor
    controllers[name] = {
        factory: constructor,
        instances: []
    };
        
    // Look for elements using the controller 
    var element = document.querySelector('[ng-controller=' + name + '] ');
    if(! element){return;
    }
        
    // Create a new instance and save it
    var ctrl = new controllers[name].factory();
    controllers[name].instances.push(ctrl);
        
        // Get elements bound to properties
        var bindings = {};
        Array.prototype.slice.call(element.querySelectorAll('[ng-bind]'))
            .map(function (element) {
                var boundValue = element.getAttribute('ng-bind');
            
                if(! bindings[boundValue]) { bindings[boundValue] = { boundValue: boundValue, elements: [] } } bindings[boundValue].elements.push(element); }); // Update DOM element bound when controller property isset
        var proxy = new Proxy (ctrl, {
            set: function (target, prop, value) {    
                var bind = bindings[prop];
                if (bind) {  
                      bind.elements.forEach(function (element) {
                        element.value = value;
                        element.setAttribute('value', value);
                      });
                }
                returnReflect.set(target, prop, value); }}); // Listen DOM element update toset the controller property
        Object.keys(bindings).forEach(function (boundValue) {
            var bind = bindings[boundValue];
            bind.elements.forEach(function (element) {
                element.addEventListener('input'.function(event) { proxy[bind.boundValue] = event.target.value; }); })}); // Fill proxy with ctrl properties // andreturn proxy, not the ctrl !
        Object.assign(proxy, ctrl);
        return proxy;
    }
        
    // Export framework in window
    this.angular = {
        controller: addController
    }
})();
          
/* User code */
function InputController () {
    this.message = 'Hello World! ';
}
        
var myInputController = angular.controller('InputController', InputController);
        
function onButtonClick () {
    myInputController.message = 'Clicked! ';   
}
Copy the code

Refer to the link

  • How can you improve your JavaScript skills by writing your own Web development framework
  • Ruan Yifeng -es6 agent