Engineering Blog

The latest tips and tricks from ModernAdvisor engineers

The ins and outs of AngularJS accessors

By Navid Boostani | April 26, 2015

Accessors are methods used to get and set the value of an object’s private data. The advantages to using accessors are well documented: future-proofing, behaviour encapsulation, and so on. Aside from these general merits, there are (at least) two Angular-specific arguments for accessor use.

Why Part 1: Template Headaches

If you read our post on partials you might be thinking that all of your scope problems have already been solved. Well unfortunately that’s not the end of the story. Let’s look at a scenario where partial isn’t enough.

Suppose we have a dropdown menu that renders a list of items.

Now let’s pretend we’re building a music app. By putting the menu in a partial it can be reused for filtering and sorting songs.

This is fairly straightforward: the partial directive inserts the template and binds data to its scope. items is a list of menu entries, and selected will store the most recently clicked entry.

In this simple case, the controller is just initializing data. Now when we select an artist from the dropdown we see:

Artist:

What happened? Well, the problem lies in ng-repeat. It’s one of the built-in directives that creates its own scope. In fact, ng-repeat instantiates a new scope for each menu item. When you click on the link, selected gets assigned to the ng-repeat scope instead of the template scope.

How do we fix it? The easiest way is to change ng-click to $parent.selected = item. This works fine for simple cases, but gets unwieldy when you start nesting directives ($parent.$parent.selected).

Alternatively, you could wrap selected in an object bound to the template scope and modify it with object.selected = item. This works because Angular scopes are objects and child scopes inherit prototypically from their parents (in the case of built-in directives anyway). Here is a snippet from the Angular source where it’s done.

When you access a primitive from a template, you’re actually reading an object property. If it’s not a direct property of the object, the property will be returned from the object’s prototype chain. On the other hand, when you set a primitive from a template, you’re writing an object property. If the property exists, it’s overwritten, otherwise it’s defined. That’s why you need a . in the names of parent properties you want to write.

If you found these last two paragraphs confusing, you’re in good company! A lot has been written on the subject so if you’re interested, you’ll find a better explanation here.

A workaround for the . requirement is to call a function in ng-click, rather than directly setting the property.

This time we have to pass select and selected to the partial.

For select we create a simple setter function.

This approach takes a little more code but enables us to change selected from anywhere in the template.

Why Part 2 – Angular Makes it Easy

Angular recently introduced a new ngModelOptions directive that takes a getterSetter option.

getterSetter: boolean value which determines whether or not to treat functions bound to ngModel as getters/setters.

This makes it really easy to pass accessor functions to form inputs, and eliminates the need for a . in ng-model.

How – A Simple Accessor

Ok, enough of the why, time to see how to use AngularJS accessors.

That’s it! You can see it in action here.

This constructor returns a function that, stores any value passed to it and returns the value when called without an argument.

Let’s update our example to make use accessors. First, switch back to the original template.

Next, some minor tweaks to the partial.

Finally, we instantiate the accessors in the controller.

That was easy. You can also pass Accessor instances to ngModel by setting getterSetter: true in ngModelOptions.

The End

If you’re here because you were having scope problems, congratulations, you’re done. If you want to unleash the awesome power of AngularJS accessors, check out these bonus features.

Bonus – Drop the $watch

Not this watch, this one. By separating the get and set methods, we can override set to avoid using $watch in our controllers.

Bonus – Memory

Sometimes it’s useful to keep track of when an attribute has changed. With a few simple functions we can add this ability to our accessor.

Bonus – One Way Only

With a few more minor tweaks, we can limit accessors to either get or set only.

The Good, the Bad, and the Non-performant

Before we put all of these features together into one mighty AngularJS Accessor, we need to talk about the elephant in the room – performance. You might have wondered: why bind all of those prototype functions to fn? JavaScript doesn’t allow you to subclass Function and we don’t want to modifyFunction.prototype, which leaves us two options: bind methods to fn, or change __proto__.

Unfortunately, there are significant drawbacks to either approach. Using Chrome’s built-in Heap Profiler, we measured the retained size of an accessor instantiated using each method. Function binding produced an object an order of magnitude larger than the mutated prototype method, and that would only worsen as methods are added. On the other hand, MDN’s __proto__ documentation starts with two big warnings:

If you care about performance you should avoid mutating the [[Prototype]] of an object.
While support for Object.prototype.__proto__ already exists today in most browsers, its behavior has only been standardized recently…

Yikes. That put enough fear in us to inspire a simple jsperf test. In the test cases we’ve seen so far, the mutated prototype actually outperformed bound methods, though our test spends a disproportionate amount of time instantiating compared with typical use.

So what does it all mean? Unfortunately, there doesn’t seem to be a silver bullet Accessor implementation that is perfect for all situations. You need to choose the one that works best for your project. Here are some general guidelines based on what we’ve learned.

  • Use the function binding method if you have a small number of Accessor methods, or will not create many accessors.
  • Mutating the prototype is appropriate if you are less concerned with speed and willing to sacrifice some browser support.
  • If you can live with accessor.rw() notation, you can avoid performance and memory penalties by not returning a function from Accessor.

A Better GetterSetter

Our final implementation defaults to mutating the prototype unless the browser doesn’t support it or safe: true is passed in the constructor options. Without further adieu, here it is.

Navid Boostani

Navid Boostani

Navid is a co-founder and CEO of ModernAdvisor. He is a problem-solver and is passionate about bringing affordable and unbiased investment management to all Canadians.