Tip: computed properties with Javascript properties

February 3, 2014    tip

First, I apologize for not updating the blog as much as expected. I am kinda busy with other stuff and I am writing an Angular book. I want to write more so I am going to find some ideas that won’t be covered in my book. Well, let’s get started.

There is a question I see a lot about Angular: Does it support computed properties? Well, there is no direct support in the framework like with Ember, but you can certainly create computed properties. Let’s see an example, the typical example:

We have someone name, in two different variables: $scope.firstName and $scope.lastName and we want $scope.fullName to print it out. Let’s see a first approach:

var app = angular.module('app', []);

app.controller('MyCtrl', function($scope) {
  $scope.firstName = "John";
  $scope.lastName = "Doe";
  
  $scope.fullName = function() {
    return $scope.firstName + ' ' + $scope.lastName;
  };
  
  $scope.counter = -1;
  
  $scope.$watch($scope.fullName, function() {
    $scope.counter++;
  });
});

To create our computed property we used a function called fullName. That function will just return a concatenation of firstName and lastName. We also created a $watch just to see how can we use or computer property on a $watch.

What about the html?:

<body ng-controller="MyCtrl">
  First name: <input ng-model="firstName" /><br>
  Last name: <input ng-model="lastName" /><br>
  Full name: {{fullName()}}<br>
  Number of times changed: {{counter}}
</body>

Try it

JS Bin


What we notice here is how we use our computed property. Instead of fullName we are doing fullName(). That is expected because fullName is a function. That works and well, there is no problem about that, but some people doesn’t like the extra () for the computed property. They feel like this is not a computed property but just a mere function (well, that is true actually). Can we do something about that? Yes!

Also, if you see the $watch syntax, what you do is pass directly $scope.fullName instead of just 'fullName'. Nothing wrong here.

Ecmascript 5 came with properties, where we can have a custom getter and setter for a concrete property. Can we use it somehow? Yes!

var app = angular.module('app', []);

app.controller('MyCtrl', function($scope) {
  $scope.firstName = "John";
  $scope.lastName = "Doe";
  
  Object.defineProperty($scope, 'fullName', {
    get: function() {
      return $scope.firstName + ' ' + $scope.lastName;
    }
  });
  
  $scope.counter = -1;
  
  $scope.$watch('fullName', function() {
    $scope.counter++;
  });
});

Here we defined an Ecmascript 5 property where we pass the object to “augment” with a property, AKA $scope and the new property name, fullName. Then we just need to define a get function. That function will return the same concatenation as before. Notice how the $watch now works passing the name of the property as a string. What about the html?:

<body ng-controller="MyCtrl">
  First name: <input ng-model="firstName" /><br>
  Last name: <input ng-model="lastName" /><br>
  Full name: {{fullName}}<br>
  Number of times changed: {{counter}}
</body>

Heey, we got rid of the (). Now it work like the other properties! We can also create a set method to be able to modify the computed property, but that is way trickier :P

Try it

JS Bin


Conclusion

Even when this is not really needed, it is a good tip to have under the sleeve.

comments powered by Disqus