Want more updates, tutorials, and awesomeness in general? Sign up!

AngularJS Sticky Notes Pt 2 – Isolated Scope

Welcome to Part 2 of the AngularJS Sticky Notes series!

In this blogpost I am going to talk about “isolated” scope as it relates to directives. Directives are one of the most powerful features of AngularJS and yet it can be one of the most confusing aspects of it as well. I believe that part of what makes directives hard to understand are the nuances surrounding scope.

Scope by default inherits from its parent scope, but this may not be desirable behavior, especially if you are building a re-usable widget. It is important that directives cannot accidentally read or write properties in the parent scope. This is where isolated scope comes in. Isolated scope does not prototypically inherit from the parent scope. It is essentially an island unto itself. So, let’s cover the basics of isolated scope and then we will talk about how I used it in the sticky notes application.

I have prepared an example to illustrate what I am going to be talking about, and you can find it here: http://jsfiddle.net/simpulton/SPMfT/

You can essentially interact with isolated scope in three ways:

1. Attributes

You can bind a isolated scope property to a DOM attribute. This sets up a one-way databinding from the parent scope to the isolated scope. If the parent scope changes, the isolated scope will reflect that change, but not the other way around. You wire this up using an @ symbol in your scope property definition in the directive.

Important note: I am binding the attribute attributeFoo to a isolated property called isolatedAttributeFoo. In most cases it is not necessary to have the properties be different names but I only did this to call out the difference between parent scope and isolated scope.

1
2
3
4
5
6
7
.directive('myComponent', function () {
    return {
        scope:{
            isolatedAttributeFoo:'@attributeFoo',
        }        
    };
})

If both my isolated property and parent property were attributeFoo, then I would simply have to do this:

1
2
3
4
5
6
7
.directive('myComponent', function () {
    return {
        scope:{
            attributeFoo:'@',
        }        
    };
})

One quick note about attribute expressions, the result is always a string since DOM elements are always strings. That is why I am using double curly braces so that string interpolation can happen, as follows:

1
<my-component attribute-foo="{{foo}}"></my-component>

Well what happens if you want two-way databinding between parent and isolated scope? Easy enough!

2. Bindings

This works almost exactly like the previous example except that you use an = sign instead of an @ symbol, as follows:

1
2
3
4
5
6
7
.directive('myComponent', function () {
    return {
        scope:{
            isolatedBindingFoo:'=bindingFoo',
        }        
    };
})

And if isolatedBindingFoo changes then it will update the property that exists in bindingFoo. The same special note applies to this example as well. If my properties in parent scope and isolated scope have the same name then the syntax is simplified.

1
2
3
4
5
6
7
.directive('myComponent', function () {
    return {
        scope:{
            bindingFoo:'=',
        }        
    };
})

But what if I want to call a function on the parent scope from isolated scope? This is a bit more tricky, but it is not going to make a grown man cry over it.

3. Expressions

The simple part first – to wire this up to call an expression on the parent scope from the isolated scope you use the & symbol, like so:

1
2
3
4
5
6
7
.directive('myComponent', function () {
    return {
        scope:{
            isolatedExpressionFoo:'&'
        }        
    };
})

And now when I call isolatedExpressionFoo in my isolated scope it serves as a wrapper to whatever I defined in my directive definition. In this case it is updateFoo. If you want to pass data through the function wrapper, you do that by passing your isolated variables through via an object map. This is why my function call looks like this isolatedExpressionFoo({newFoo:isolatedFoo}).

1
2
<input ng-model="isolatedFoo">
<button class="btn" ng-click="isolatedExpressionFoo({newFoo:isolatedFoo})">Submit</button>

And then! updateFoo gets called and does something incredibly clever.

1
2
3
4
5
.controller('MyCtrl', ['$scope', function ($scope) {
    $scope.updateFoo = function (newFoo) {
        $scope.foo = newFoo;
    }
}]);

So, that is our whirlwind tour of isolated scope!

The Application!

This will actually be fairly brief since we covered the nuts and bolts of isolated scope already.

For reference purposes here is the fiddle:
http://jsfiddle.net/simpulton/VJ94U/

The application has isolated scope in two places, the first being the myNotebook directive, and the second being the myNote directive. What is interesting is that I am setting isolated scope within isolated scope. I am binding notes from NotebookCtrl to the myNotebook directive and from there I am looping over notes and binding note from myNotebook to the myNote directive. The reason I am using two-way databinding instead of an attribute expression is because I am dealing with objects and so string interpolation would not be optimal.

The other problem that I faced was I wanted to be able to initiate the delete process from the note directive without having to reach out of its isolated scope. Essentially I am having to pass through two layers of functions to get back up to the scope, but I managed to do it while keeping my hands in the ride at all times. From the note directive, I call delete which is a wrapper that calls onDelete which is a wrapper that calls deleteNote. Tada!

Conclusion

The majority of this post was spent breaking down isolated scope and how it works. Once I realized that the syntax had been simplified to basically three options, the lights just kind of went on. From there it was fairly self-evident why isolated scope in AngularJS Sticky Notes was wired up the way it was. Two-way binding and function wrappers all the way down. I am still in awe that isolated scope worked so well even when I went all Inception on it and started creating isolated scope within isolated scope.

A special thanks to John Lindquist, Vojta Jína and Ken Slachta for reviewing my post and examples before I released it to the wild.

Resources

AngularJS Sticky Notes Fiddle
http://jsfiddle.net/simpulton/VJ94U/

AngularJS Sticky Notes Repository
https://github.com/simpulton/angular-sticky-notes

AngularJS Directive Documentation
http://docs.angularjs.org/guide/directive

AngularJS Mailing List
https://groups.google.com/forum/?fromgroups#!forum/angular

32 comments… add one

Leave a Comment