AngularJS Extend and Computed Property
This is an Intermediate level discussion on AngularJS Extend, Computed Property in general, and a fairly complicated binding bug that needs some new learning.
I came from a KnockoutJS world. In the KnockoutJS world which I'm familiar with, there is the concept of a Computed Property.
KnockoutJS Computed Property
Because native Javascript objects does not raise OnPropertyChanged events, KnockoutJS wraps properties with an observable wrapper.
A class would look like this:
The developer takes care of the properties firstName and lastName, and KnockoutJS understands when firstName or lastName changes, fullName needs to be re-evaluated.
AngularJS binds Native Javascript Object
In AngularJS, there is no direct concept of a Computed Property, instead, AngularJS watches various binding expressions and automatically re-evaluate expressions. Since AngularJS binds to native Javascript directly, it employs a dirty checking mechanism.
A native person might look like this:
And a View would look like this:
Defining Class for AngularJS
But very quickly, one would find that this gets very unwieldy, and you end up duplicating expressions everywhere in your View. Duplicated code is never good. So we need to tidy up the definition of the model a bit more.
AngularJS Extend
Such definitions are nice and good, but you will need a way to populate these when you pull your data back from a REST service as JSON. Luckily, AngularJS provides both angular.extend as well as angular.merge helper methods to get you going quickly.
If you are familar with jQuery, this is nearly identical to jQuery.extend, it copies the properties from the later objects onto the first one.
The updated View is then:
Extending Arrays
When you have a collection of people, the code needs to look like this:
This looks good initially, except there are a few problems:
- angular.extend does not know anything about Person class, and fullName is not available
- angular.extend will override people[0] with jsonPeople[0] (not null). So the result still does not define fullName()
So you need something like this:
A subtle bug in copying with extend
The Computed Property doesn't work because the way angular.extend copies references, people[i].fullName is copied to (new Person()).fullName, which means that the (new Person()).fullName is calculating from the previous people[i].fullName, because it uses the 'self' reference.
(let me know if I need to draw a picture)
There are two ways to fix this:
1. We can fix how we extend existing records.
2. We can fix how we define the Person class. I think this is the better method.
This bumps the fullName method to the prototype/supertype, so it is no longer an immediate property defined on the Person object.
Summary
- Notes on doing computed property in AngularJS
- How to define a Class for AngularJS
- How to handle extend and extend an array
- How to move computed methods to the prototype
- Plunker http://plnkr.co/edit/HcuxnT3EAVrtz1PyWwXb check out the two Count1 and Count2 computed properties and how they work differently after you clicked 'update'