Beware of nested scopes in AngularJS

AngularJS uses scopes as a glue between the view (i.e. the HTML page) and the controller. A scope is nothing more but a simple JavaScript object, where you can set different properties on. When the logic in your controller is executed, you can inject the scope and set properties to it. Afterwards, the corresponding view will be able to bind these properties to its HTML elements. However, when one uses scopes to bind data to form elements (f.x., text boxes), there can be some unexpected issues that I will try to explain in the following post.

The nature of scopes

It is interesting to see how these scopes are created. Every AngularJS application has exactly one root scope. Every scope should be created from a parent one and it becomes a child to this parent. Moreover, a child scope inherits prototypically its parent (prototypical inheritance). This means that if your view is bound to a property, called name, then AngularJS will try to locate this property on the given child scope first. If the scope does not contain such a property, it will try to locate the property on its parent. This continues until the root scope is reached. Let’s take a look at the following example:

If you examine the $scope object in, f.x. Chrome developer tools, you will not see a property, called rootMessage, on that object. Furthermore, if you take a look at the generated HTML, you will be able to distinguish the regions where each scope applies to.

AngularJS HTML generated code

Scopes and form elements

Knowing what the nature of scopes is, we can take a look at some special HTML elements – the form elements. These are the elements we deal with when filling out forms: input elements (text, hidden, radio, etc.), select, textarea. These elements are special, because they can change an AngularJS model, i.e. you can attach a model to an input element and when the user enters something into that element, your model will be updated automatically. Let’s check the following fiddle:

If we try to edit the content of both fields, we may get something like this printed: Hello, Jim 27. What if our business rules require that when the name is Tom, the age field should be omitted? This can be achieved in many ways. AngularJS provides two similar (but still VERY different as we are going to see) directives – ng-show and ng-if.  Both of them take a boolean expression to determine whether their children should be shown. The difference comes from the way they do this: ng-show manipulates the CSS style via classes (it adds a class, called ng-hide, if the boolean expression is false), while ng-if destroys the entire HTML DOM if the expression is false. Let’s try it: first we add the following attribute to the div element that contains the age input field: ng-show=”name != ‘Tom'”.  The result is shown below.

AngularJS example with ng-show

Now if we replace ng-show with ng-if we would of course get the same result when the name is Tom. However, if we try to write Jim instead and then try to enter an age…

AngularJS example with ng-if

Hey, why isn’t the age displayed? Well, it is all due to the nature of scopes. What I didn’t tell you earlier is that ng-if creates a child scope for its HTML region. This means we have tried to bind a property, age, that does not exists on our scope; it exists on its parent. So we write to another property, which is set on this child scope (although it has the same name as the one on the parent scope). If you have a look at the generated HTML, you will see the following:

AngularJS example with ng-if HTML

Instead we can do something else: we can wrap our variable in an object. This was AngularJS will do a read first on that object (which will resolve the right property) and then write to its property. It looks like this

Conclusion

Scopes are a very simple but still effective way to connect controllers and views in AngularJS. In a medium-sized project you would get a very big tree of scope objects (starting with the root scope). There are many directives that create their own scopes and this can cause you problems if you are not cautious. When dealing with modifiable properties, always wrap them in objects to ensure they work even if you do changes in the view.