Mocking AngularJS: How to create our own watch function

Sunday, 3 September 2017
AngularJS is awesome! Yeah, it is, even after so many competitors it still did not lose its value and manages to be on top of others. So we all love it and wonder how it can be so cool, same goes for me. The one thing I really inspired with is Angular's watch function which automatically gets called when object bind with $scope got changed. So it's inevitable for me to learn how watch function works and by learning this I come up with the idea that how I can create my own simple watch function with simple JavaScript.

Before starting this tutorial I would like to inform you that this is not about how Angular's watch function works neither I am saying that this is how Angular watch works. There are many web articles present on this topic and you can easily find them using Google. Instead of it, this post focuses on how we can create our own function which acts like Angular's watch function up to some extent!

So the story starts with JavaScript Objects. Let's create one
 var obj = {};  

Now as we know we can attach properties to any JS object but do you know in how many ways we can attach them?

1#
obj.a = 1;  

2#
 obj['b'] = 2;  

These two ways are very common and most used but there is another unconventional way which is Object.defineProperty. So it works like this
Object.defineProperty(object, propertyToBeAttached, configuration)

so if we use Object.defineProperty we add a property like this
 Object.defineProperty(obj, 'c', {   value: 3 });

If property already attached with object then Object.defineProperty also use to modify its configuration but why go for this complicated method when we can simply create property with [.] notation?Object.defineProperty provides many extra options to control properties of object, you can read about it on MDN [please read it first if you don't know about it]. But real fun starts with set and get methods for property provided by Object.defineProperty. TLDR version of set and get methods is get method invokes automatically every time when you try to access property's value and set method invokes when you try to set value of property. And yes you get it right we need set and get method of property being watched by our watch function so hold this in your mind.

Now let's begin

First of all, we need $scope so let's initialize it
var $scope = {};

Now we need watch function as a property of our $scope object and Angular's watch function takes two mandatory arguments first one is property to be watched another one is handler function and handler gives us the new and old value of the property. At first its very easy to attach watch function with $scope
 var $scope = {}; $scope.watch = function (propToWatch, handler) {};  

but we can change watch value any time intentionally or accidentally ☹ like this
 var $scope = {};
$scope.watch = function (propToWatch, handler) {}; 
$scope.watch = 1;  

But with Object.defineProperty we can also lock properties so why shouldn't we do it
 
var $scope = {};
Object.defineProperty($scope, 'watch', {
    value: function (varToWatch, handler) {},
    writeable: false,
    configurable: false
});

Now as we have a property which we are going to watch so next step is we need its set and get methods so let's use Object.defineProperty to modify varToWatch and add set and get methods
 
var $scope = {};
Object.defineProperty($scope, 'watch', {
    value: function (varToWatch, handler) {
        Object.defineProperty($scope, varToWatch, {
          set: function (newValue) {},
          get: function () {}
        });
    },
    writeable: false,
    configurable: false
});

As I mentioned earlier set is going to call every time we set the value of the property so this is the point when we will invoke our handler function
 
var $scope = {};
Object.defineProperty($scope, 'watch', {
    value: function (varToWatch, handler) {
        Object.defineProperty($scope, varToWatch, {
          set: function (newValue) {
            handler(newValue);
          },
          get: function () {}
        });
    },
    writeable: false,
    configurable: false
});

But now what about old value? Its easy and this is final steps and our watch function is ready ;)
 
var $scope = {};
Object.defineProperty($scope, 'watch', {
  value: function (varToWatch, handler) {
    var oldValue = $scope[varToWatch];
    Object.defineProperty($scope, varToWatch, {
      set: function (newValue) {
        handler(newValue, oldValue);
        oldValue = newValue;
      },
      get: function () {
        return oldValue;
      }
    });
  },
  writeable: false,
  configurable: false
});

Here we are caching property value from $scope in an oldValue variable and we don't care about if it's already defined or not as it will set undefined if the property is not set yet! Here you go we have created our very own simple watch function which really works we can use it just like we Angular's watch
 
$scope.a = 1;
$scope.watch('a', function (newValue, oldValue) {
  console.log(newValue, oldValue);
});

That's all folk you can play with it here
Thanks for ready! Your comments are welcome and happy coding!

2 comments