found drama

get oblique

AngularJS: overview of directive scopes

by Rob Friesel

AngularJS: Superheroic JavaScript Framework by GoogleI gave a talk on AngularJS directives recently and a good portion of that talk focused on defining the types of scopes that those directives can have. As something that the talk’s attendees could refer back to, I decided to put together this short blog post. It captures the spirit (if not the exact words) of what I presented that day. So without further ado…

scopes: an overview

One way to think about scope objects on an AngularJS directive is to think of it as a little sandbox for the directive’s model data. The directive is able to participate in the application’s $digest cycle, but we’re also able to carve off pieces that we can safely modify without “polluting” or “damaging” the parent scope. (Except when we want to.) This safety is the main reason that I’ve advocated so strongly for the liberal use of directives in AngularJS application design. When defining a directive, the value (or lack thereof) assigned to the scope property of the directive definition object will determine what type of scope is created. At a high level, these scopes include: parent, new, and isolate.

parent scope

This one’s easy. If we leave off the scope property from the directive definition object, then it (the directive) does not create a new scope, and instead inherits one from its parent (e.g., the controller). This implicitly creates a two-way binding for all properties on the parent scope; there is no inheritance, we’re simply dealing directly with the same scope as our parent.

new scope

This one’s only slightly trickier. If we set scope:true on the directive definition object, then AngularJS kicks in the prototypal inheritance and creates a new version of the parent scope for the directive. I’d say that this is a quick way to create a directive with one-way bindings for all the properties on the parent scope but… When we stop and consider how JavaScript’s prototypal inheritance works with reference types, it’s easy to see where we would (potentially) inadvertantly start screwing up data on our parent scope. Observe the difference between how the scope bindings work for the ddc-presenter and ddc-foo directives in this fiddle:

isolate scopes

Where the AngularJS scope directives really shine is in the isolate scopes. These are those safe little sandboxes I alluded to earlier. By declaring the scope property on the directive definition object with an object, we are creating an entirely new scope that is isolated from the parent with no inheritance from it. While this doesn’t necessarily mean that we can manipulate data with impunity, it does offer us some protection against accidentally mangling data from our parent scopes. We still have access to our parent scope (through the $parent property) if we really need it, but we’re no longer working with it incidentally.

The other important thing to know about isolate scopes is that properties are declared with one of three special symbols to indicate the type of binding. Those symbols are:

  • @ for one-way bindings
  • = for two-bindings
  • & for expression bindings

@

The @ (one-way) binding uses interpolated literals to initialize a value on the directive’s isolate scope that is a copy of that value from the parent scope. The “copying” takes place during the directive’s linking phase and once complete, we can update that value indefinitely without overwriting the value on the parent scope. Observe:

=

The = (two-way) binding allows us to keep values synchronized between the directive’s isolate scope and the parent scope. During the linking phase, a reference is created between the two scopes such that updates to one will reflect in the other (and vice versa). In some ways, this is what we get with the parent scope option (above) except that we are making explicit bindings to a specific subset of the properties on the parent scope. Observe:

&

The & (expression) binding allows us to call functions from the parent scope within the context of the isolate scope. This allows us to create generic directives effectively have an interface that needs to be implemented with methods from that parent scope. Observe:

To sum up

AngularJS directives provide three different scopes to use for interacting with the data from parents scopes. We can omit the scope property when defining our directive and simply use the parent scope. We can set scope:true to create a new scope that prototypically inherits from the parent. And lastly, we can create an isolate scope to create a “sandboxed” context for the directive’s data. While all three have their uses, we should prefer the isolate scope for the flexibility and safety that it gives us.

About Rob Friesel

Software engineer by day, science fiction writer by night. Author of The PhantomJS Cookbook and a short story in Please Do Not Remove. View all posts by Rob Friesel →

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*