Oct 29, 2014

Maybe the desire to control is the problem

I love to play tennis. Serve was the biggest weakness in my game. So I practice a lot, and now I am getting better and better in my serve. One of the most important techniques in serving is pronation which I struggled most with until I saw this video, The Serve Pronation Technique And 7 Drills To Learn It. Here is what strike me most in the article.

Control and letting go are, of course, at the opposite ends of the spectrum; the more you want to control, the less you will let go. While it makes logical sense to try to hit the court when you work on your serve, this desire is the BIGGEST OBSTACLE to improving your serve. You absolutely need to practice your serve technique and NOT aim in the court. Think speed, not hard. Let go, don’t control.

So why am I mention this? As a human being, we all hate to be controlled. Ironically, we all tend to love to control. This is why the world has so many conflicts. As a developer especially a front end developer, the desire to control is counter-productive. In the following, I will use a very simple example to illustrate why the controll mindset is so bad. This example was originally used by knockout.js. In the application, a user can click on a button, and two labels and another button can show or hide. It is simple but tricky enough for me illustrate my point. The following implementation in jsbin just uses vanilla jQuery. The following code is very bad, so I just want to show you part of it, I hope you can follow through.

 btnAdd.click(function () {  
    countClick++;     
    lblCount.text(countClick);
    
    if (countClick >= 3) {
      divWarning.show();
      btnAdd.attr("disabled", true);
    } else {
       divWarning.hide();
       btnAdd.attr("disabled", false);
    }
    
    lblPlural[countClick > 1 ? "show" : "hide"]();
    
  });

When I wrote this pieces of code, I was thinking from the perspective of CPU, and my desire to control is very strong. Why this code is bad? This is because a single handler has too many things to keep track. Even in this very simple application, the code is very smelly. Once the application grows bigger, the complexity of the handler will explode. One way to mitigate the problem is to use the "divide and conquer" strategy, where an upper controller delegates the control to a lower controller, until complexity is small enough to control, sometimes this works like MVC. But lots of times, this complexity is still hard to manage. Very often, Model, View, Controller still creates conflict, controller is still a bottleneck of communications.

hm.js

About three years ago, I was thinking whether it's possible to get rid of controller completely, so I developed a JavaScript library hm.js, I call it Model-View-Subscription library. The following is an implementation in jsbin using hm.js. The following is the view.

<div>You've clicked <span id="lblCount"></span> time
  <span id="lblPlural">s</span></div>

 <button id="btnClick" >Click me</button>

 <div id="divWarning">
  That's too many clicks! Please stop before you wear out your fingers.
  <button id="btnReset">Reset clicks</button>
 </div>

Here is the model

hm( "clickApp", {
  clickCount: 0, 
  overClicked: function() { 
    return this.get( "clickCount" ) >= 3; 
  }, 
  plural: function() { 
    return this.get( "clickCount" ) > 1; 
  } 
} );

Here are the subscriptions

$(function () {
  
  var model = hm("clickApp");
  
  //model subscribe view, handler change the subscriber
  model.cd("clickCount").sub($("#btnClick"), "click", function (e) {
      this.set(this.get() + 1);
  });
  
  //view subscribe model, handler change subscriber
  $("#btnClick").sub(model.cd("overClicked"), "afterUpdate", function (e) {    
    this.attr("disabled", e.publisher.get());
  });
  
  //view subscribe model, handler change subscriber
  $("#lblCount").sub(model.cd("clickCount"), "init afterUpdate", function (e) {
    this.text(e.publisher.get());
  });
  
  //view subscribe model, handler change subscriber
  $("#lblPlural").sub(model.cd("plural"), "init afterUpdate", function (e) {
    
    this[e.publisher.get() ? "show" : "hide"]();
  });
  
  //view subscribe model, handler change subscriber
  $("#divWarning").sub(model.cd("overClicked"), "init afterUpdate", function (e) {
    
     this[e.publisher.get() ? "show" : "hide"]();
  });
  
  //model subscribe view, handler change subscribe
  model.cd("clickCount").sub($("#btnReset"), "click", function () {
     this.set(0);
  });
  
});

The code may be a bit long, but it is actually very straightforward. When I wrote the above code, I was thinking from the perspective of subscribers, where I have not desire to control others, I just want to control myself. Unlike normal event handling, all subscription has explicit publisher and subscriber. Unlike normal event handler, subscription handlers all update the subscriber but never update the publisher or any other object. Also inside of the handler, there is no hard reference to DOM or model, this make the handler very portable. Because of the loose coupling between model and view, the library can handle a very complicated view. The library also supports declarative subscription. The implementation of declarative subscription is in JSBIN. And you can remove the above subscription code.

 <div>You've clicked <span text='clickCount'></span> time<span
  show="clickCount|>1">s</span></div>
 <button ++='clickCount' disable='overClicked'>Click me</button>
 <div show="overClicked">
  That's too many clicks! Please stop before you wear out your fingers.
  <button 0="clickCount">Reset clicks</button>
 </div>

AngularJs

AngularJs is the most popular Single Page Application framework nowadays. But some people complain that the learning curve is deep. But if you appreciate the benefit of subscription, you can easily avoid the pitfall and adopt the best practice.

People call AngularJs a MVC framework, I don't know why. Is it because it has a component call controller? If you have enough experience in using Angular, you will find that controller actually does not control anything. It is just a place you write code to expose model to views and directives via an object called scope. I don't know why Angular call it controller, maybe they are running out of good name, or maybe they want to stick with popular name "controller". I would rather call it "model exposer". The model is purely Plain old JavaScript Object (POJO), it does not contain any view related code. The interaction between view and model is through subscriptions, which is created in directives. In the following demo implemented using Angular, I don't use any built-in directive "ng-xxx" and use custom directive in order to illustrate this subscription concepts in AngularJs.

Here is the model exposed by controller.

var app = angular.module("myApp", []);
  
  app.controller("modelExposer", function ($scope) {
    angular.extend($scope, {
      clickCount: 0,
      overClicked: function () {
        return this.clickCount >= 3;
      },
      plural: function () {
        return this.clickCount > 1;
      }      
    });
  });

Here are the directives, which create the subscriptions between model and view.

app.directive("myClick", function () {
    return function (scope, $elem, attrs) {
      var expression = attrs.myClick;      
    
      
      /*
      model subscribe view, handler change subscriber only
      the pseudo code that I want to write is 
      
      scope.subscribe($elem, "click", function () {
        this.$eval(expression)
       });
       
       */
      $elem.click(function () {
        scope.$apply(function () {
          scope.$eval(expression);
        });
      });      
    };    
  });
  
  app.directive("myText", function () {    
    return function (scope, $elem, attrs) {      
      /*
      view subscribe model, handler change subscriber
       the pseudo code that I want to write is 
      
      $elem.subscribe(scope, attrs.myText, "change", function (value) {
        this.text(value)
       });
       
      */
      scope.$watch(attrs.myText, function (value) {
        $elem.text(value);
      });      
    };
  });
  

  app.directive("myShow", function () {
    return function (scope, $elem, attrs) {      
         /*
      view subscribe model, handler change subscriber
       the pseudo code that I want to write is 
      
    $elem.subscribe(scope, attrs.myShow, "change", function (value) {
        this[value ? "show" : "hide"]();
       });
       
      */
        scope.$watch(attrs.myShow, function (value) {          
          $elem[value ? "show" : "hide"]();      
        });   
    };    
  });
  
  app.directive("myDisabled", function () {
    
    return function (scope, $elem, attrs) {      
      /*
      view subscribe model, handler change subscriber
       the pseudo code that I want to write is 
      
 $elem.subscribe(scope, attrs.myDisabled, "change", function (value) {
        this[value ? "show" : "hide"]();
       });
       
      */
      scope.$watch(attrs.myDisabled, function (value) {
        $elem.attr("disabled", value);
      });    
    };
    
  });

Here is the view, which is decorated with the directives above.

<div ng-app="myApp" ng-controller="modelExposer">

 <div>You've clicked <span my-text="clickCount"></span> time
  <span my-show="plural()">s</span></div>

 <button my-click="clickCount = clickCount + 1" 
     my-disabled="overClicked()">Click me</button>

 <div my-show="overClicked()">
  That's too many clicks! Please stop before you wear out your fingers.
  <button my-click="clickCount = 0">Reset clicks</button>
 </div>
</div>

AngularJs does not mention that view and model are connected by subscriptions, it uses traditional event handling. Neither does it explicitly suggest that the handler should only change subscriber, because there is no explicit subscriber in event handling. So I have put some pseudo code in the comment of directives. The pseudo code is the subscription code that I want to write, but AngularJs does not support them. However, Misko Hevery, the father of AngularJs does understand that subscription is good. He gave a talk of AngularJS MTV Meetup: Best Practices, he mentioned that

  • Treat the scope as read-only in views
  • Treat the scope as write-only in controllers

If you don't have the subscription concept, it may confuse you. But once you understand the subscription concept as shown above, it is very easy to understand. Let's modify it a little bit.

  • Treat the scope as read-only in handler where view subscribes model
  • Treat the scope as write-only in handler where controller (model) subscribes view

What this means is that, when view subscribes scope (exposing model) change event, the handler should not change model, so it is read-only (it should change view). When model subscribes view event, handler should change model, so it is write-only (it should not change view).

In summary, subscription is good, because it forces you to think from the perspective of subscriber, and you tend to write handlers which only affect subscribers and don't control on others. And we can live without controller.