Object.defineProperties()

After leaving it alone for a while, I have recently been working heavily in Angular again so it seemed I should review the Angular best practices and see what they are like now. The two major changes I have noticed since the beginning of the year was that instead of using $scope we are using controllerAs and using the controller as an object (I am a big fan of this one), and avoiding $watch for performance reasons.

What I want to focus on is how we can get around using $watch. At first I was confused how we could remove $watch. There are just too many times when we have to perform an action when something changes. There are times where we can set a trigger on a change event. But what about the times when the data can change from multiple directions? Do we have to add code everywhere that the value can change, when we do an update from the server's API, the three or four input fields scattered through the app, every new widget we add, and in every piece of code that might affect it?

The answer is no and the reason is by switching to controllerAs and and stopping our use of $watch we are actually going to be using built in JavaScript features, which seems to be preparing for the way Angular 2 does things.

How do we replace $watch?

So what is a feature in JavaScript that lets us get around $watch in Angular? Well if you are clever you may have noticed the name of this article and figured it out.

Yes, Object.defineProperties() is what comes to our rescue.

Object.defineProperties() allows us to create or edit properties on an object (you can also use Object.defineProperty() if you are only modifying a single property). Object.defineProperties() gives you lots of control, to see all the options see the MDN section on it. But for our purposes we are going to focus on using the getters and setters. By changing the getters and setters we can change what happens when you read or write to a property.

So in this case we have a variable ftIn.measure which contains a measurement in inches. However, in the form we want to use feet and inches for a better user experience. So we create a new object called ftIn.displayMeasure and we use the feet and inches properties in the form. Our Object.defineProperties() to make this happen is:

Object.defineProperties(ftIn.displayMeasure, {
  feet: {
    get: function() {
      return Math.floor(ftIn.measure / 12);
    },
    set: function(val) {
      ftIn.measure = val * 12 + this.inches;
    }
  },
  inches: {
    get: function() {
      return Math.floor(ftIn.measure % 12);
    },
    set: function(val) {
      ftIn.measure = this.feet * 12 + val;
    }
  }
});

In here you notice that we get the value from the ftIn.measure and convert it to feet or inches for display and the when we change the values in the form we update the ftIn.measure to based off the incoming value and the value of the other property.

Another nice feature of this is that instead of having to manually update the inches and feet if they insert something greater than 12 the feet and inches are both recalculated by Angular when either changes.

Here is a Plunker with all the code:

Conclusion

While I am doing this in Angular I hope you can see how powerful changing the getters and setters in an object can be in allowing for more dynamic and interactive code.

comments powered by Disqus