Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at the ColdFusion Centaur And Bolt User Group Tour (Jun. 2009) with: Ye Wang and Simon Free and Spencer Strickland
Ben Nadel at the ColdFusion Centaur And Bolt User Group Tour (Jun. 2009) with: Ye Wang , Simon Free@simonfree ) , and Spencer Strickland

Breaking Changes In Isolate-Scope Behavior In AngularJS 1.2

By Ben Nadel on

With the release of AngularJS 1.2, there was a fairly large breaking change in the way that Isolate-scope directives interacted with child elements in the DOM (Document Object Model). And, while this may not matter to most people using AngularJS, I still run a lot of production code through AngularJS 1.0.8, so this is an important point for my team. In AngularJS 1.0.8 (and earlier), child elements [may] receive the isolate scope even when they are not part of the isolate directive.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

There's a growing movement, it seems, in the AngularJS community that you should "isolate all the things!" And, while I understand the sentiment, applying that philosophy in an AngularJS 1.0.8 application can actually break the application. The reason for this is that you will unwittingly tie child-element scope references into the isolate scope. Before AngularJS 1.2, the isolate scope "leaks" into the contextual-descendant DOM branch when you are not using transclusion.

To see this in action, I've put together a super simple demo that uses a Controller and an isolate-scope directive and attempts to interpolate a message.

Here is the version running AngularJS 1.0.8:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Breaking Changes In Isolate-Scope Behavior In AngularJS 1.2
  • </title>
  • </head>
  • <body ng-controller="AppController">
  •  
  • <h1>
  • Breaking Changes In Isolate-Scope Behavior In AngularJS 1.2
  • </h1>
  •  
  • <!--
  • Both the AppController and the bnIsolate directive define a scope
  • reference for "message". In AngularJS 1.0.8, the message will be taken
  • from the isolate scope.
  • -->
  • <p bn-isolate>
  • <strong>AngularJS 1.0.8</strong>: {{ message }}
  • </p>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.0.8.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I control the root of the application.
  • app.controller(
  • "AppController",
  • function( $scope ) {
  •  
  • $scope.message = "Hello from App Controller.";
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I control the an isolate scope directive.
  • app.directive(
  • "bnIsolate",
  • function() {
  •  
  • // I bind the JavaScript events to the local scope.
  • function link( scope, element, attributes ) {
  •  
  • scope.message = "Hello from Isolate Scope.";
  •  
  • }
  •  
  •  
  • // Return the directive configuration. Notice that we are creating an
  • // isolate scope, but not binding any values or expressions.
  • return({
  • link: link,
  • restrict: "A",
  • scope: {}
  • });
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, the "{{ message }}" is inside the isolate-scope, lexically, but not in the isolate scope template. When we run this page, we get the following page output:

AngularJS 1.0.8: Hello from Isolate Scope.

As you can see, the main page interpolation is pulling the {{ message }} from the isolate scope even though it is not part of the isolate scope directive.

Now, here is the exact same demo in which the only thing we've changed is the version of AngularJS that we are running (1.2.26):

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Breaking Changes In Isolate-Scope Behavior In AngularJS 1.2
  • </title>
  • </head>
  • <body ng-controller="AppController">
  •  
  • <h1>
  • Breaking Changes In Isolate-Scope Behavior In AngularJS 1.2
  • </h1>
  •  
  • <!--
  • Both the AppController and the bnIsolate directive define a scope
  • reference for "message". In AngularJS 1.2.26, the message will be taken
  • from the AppController since the child elements don't get the isolate scope
  • unless they are part of the isolate scope template.
  • -->
  • <p bn-isolate>
  • <strong>AngularJS 1.2.26</strong>: {{ message }}
  • </p>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.2.26.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I control the root of the application.
  • app.controller(
  • "AppController",
  • function( $scope ) {
  •  
  • $scope.message = "Hello from App Controller.";
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I control the an isolate scope directive.
  • app.directive(
  • "bnIsolate",
  • function() {
  •  
  • // I bind the JavaScript events to the local scope.
  • function link( scope, element, attributes ) {
  •  
  • scope.message = "Hello from Isolate Scope.";
  •  
  • }
  •  
  •  
  • // Return the directive configuration. Notice that we are creating an
  • // isolate scope, but not binding any values or expressions.
  • return({
  • link: link,
  • restrict: "A",
  • scope: {}
  • });
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

Now, when we run this page, we get the following output:

AngularJS 1.2.26: Hello from App Controller.

As you can see, the main page interpolation is [correctly] pulling from the AppController, which is more likely to be what the developer intended.

There are things you can do to help each version of AngularJS act like the other (see the vide). But, for the most part, this should make you think twice about using isolate-scope directives in AngularJS 1.0.8. I'm not saying don't use them - just don't use them haphazardly.




Reader Comments

<blockquote>There's a growing movement, it seems, in the AngularJS community that you should "isolate all the things!"</blockquote>

I adopted this mentality as well, but unfortunately, you quickly run into problems when applying two directives, each with their own isolate scope, to the same DOM element...

Reply to this Comment

@Vincent,

Ah, good point! A single element can only have one normal scope and one isolate scope, from what I recall. But, do the two isolate directives *share* the same isolate scope? Or does it throw an error? I can't remember off-hand.

Reply to this Comment

@Ben,

It throws an error... So although I feel like I'd rather making pretty much every directive have an isolate scope, I am very reluctant to do so :(

Reply to this Comment

@Vincent,

Yeah, especially if two isolate-scopes on the same directive will raise an error. That's good to know, thanks for checking!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.