Thursday, March 5, 2015

Two-Way Data Binding in Angular

Aaron Starkston, Lead Developer, Product Development

I have found myself working quite a bit with Angular’s front-end framework. I find it extremely useful for creating a great experience for any data-driven application- the repeaters, dependency injection, what’s not to like? One of its features I have grown to love is utilizing its data binding capabilities between your scope and HTML. When done right, the two seamlessly interact with each other; when done wrong, who knows what can happen.

If you have any Angular experience, one of the first things you probably did was get your app working with one-way data binding. One-way data bindings in Angular are tough to get lost in. The value displayed in the view stays consistent with the value set in a controller or directive. On the other hand, two-way data bindings are easy to implement but can sometimes be difficult to maintain. Let’s take a look at a basic example of two-way data binding.

That example is about as basic as two-way data binding gets. Now, I’m sure you’re asking yourself, “Well, where can I go wrong?” Here are two pitfalls I’ve seen when leveraging Angular’s data binding that will help you avoid some future headaches:

1. Using primitives instead of objects when binding with ng-model

As we saw in the previous example, you can bind an object’s property to the ngModel directive and display it in a div. Neat, right? Notice how we bind only to an object’s property ($scope.friendly.greeting), and not just some $scope.greeting string we set in our controller.

We’re not doing this just because the wonderful folks who made Angular told us to. It’s important that we avoid binding primitives with ng-model because of Javascript’s prototypal inheritance. If we write to a primitive in our child scope, it gets pushed up to the parent scope. You might not notice a difference if there’s only one child scope; however, once you have multiple child scopes, your scope will slowly become chaotic and unmanageable.

Rule of thumb: bind ng-model to objects, not primitives

2. Accidental pointers to data-bound objects

There are a few occasions I have seen the need to reset some object in the model, mainly using objects that need to be reset for the user upon closing a window or toggling some element in the DOM. Typically I’ve seen the following example happen for anything that involves user submission. Let’s take a look at a second example to what’s going on.

We have two objects here- one object the user interacts with directly and one object we want to keep inside to the controller. You’ll notice that the first time we hit ‘Change greeting’, the value updates to masterObj as we planned it to. If you keep entering values and try and change it, we’re still able to update $scope. masterObj; however, when we set our master object equal to $scope.friendly, we actually just created a pointer to that object instead. Every time $scope.friendly updates, $scope. masterObj updates.

Yikes! Thankfully, it’s easy to avoid this pointer issue. Angular has a really useful function called angular.copy(source, [destination]). This function makes a deep copy of an object instead of creating a reference pointing to that object. Include an angular.copy() around your object assignment, or just angular.copy the source and assign it to your master object, and you’re good to go.

Rule of thumb: set scope objects using angular.copy() to avoid pointer issues