Scopes in AngularJS are bound to the same rules of prototypical inheritance as plain old JavaScript objects. When wielded properly, they can be used very effectively in your application, but there are some "gotchas" to be aware of that can be avoided by adhering to best practices.
Getting ready
Suppose that your application contained the following:
(app.js)
angular.module('myApp', [])
.controller('Ctrl', function() {})
(index.html)
<div ng-app="myApp">
<div ng-controller="Ctrl" ng-init="data=123"> <input ng-model="data" /> <div ng-controller="Ctrl"> <input ng-model="data" /> </div> <div ng-controller="Ctrl"> <input ng-model="data" /> </div> </div> </div>
How to do it…
In the current setup, the $scope instances in the nested Ctrl instances will prototypically inherit from the parent Ctrl $scope. When the page is loaded, all three inputs will be filled with 123, and when you change the value of the parent Ctrl <input>, both inputs bound to the child $scope instances will update in turn, as all three are bound to the same object. However, when you change the values of either input bound to a child $scope object, the other inputs will not reflect that value, and the data binding from that input is broken until the application is reloaded.
To fix this, simply add an object that is nested to any primitive types on your scope. This can be accomplished in the following fashion:
(index.html)
<div ng-app="myApp">
<div ng-controller="Ctrl" ng-init="data.value=123"> <input ng-model="data.value" />
<div ng-controller="Ctrl">
<input ng-model="data.value" /> </div>
<input ng-model="data.value" /> </div>
</div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/obe24zet/ Now, any of the three inputs can be altered, and the change will reflect in the other two. All three remain bound to the same $scope object in the parent Ctrl $scope object. The rule of thumb is to always maintain one layer of object indirection for anything (especially primitive types) in your scope if you are relying on the $scope inheritance in any way. This is colloquially referred to as "always using a dot."
How it works…
When the value of a $scope property is altered from an input, this performs an assignment on the $scope property to which it is bound. As is the case with prototypical inheritance, assignment to an object property will follow the prototype chain all the way up to the original instance,
but assignment to a primitive will create a new instance of the primitive in the local $scope property. In the preceding example, before the .value fix was added, the new local instance was detached from the ancestral value, which resulted in the dual $scope property values.
There's more…
The following two examples are considered to be bad practice (for hopefully obvious reasons), and it is much easier to just maintain at least one level of object indirection for any data that needs to be inherited down through the application's $scope tree.
It's possible to reestablish this inheritance by removing the primitive property from the local $scope object:
(app.js) angular.module('myApp', []) .controller('outerCtrl', function($scope) { $scope.data = 123; }) .controller('innerCtrl', function($scope) { $scope.reattach = function() { delete($scope.data); };
}); (index.html) <div ng-app="myApp"> <div ng-controller="outerCtrl"> <input ng-model="data" /> <div ng-controller="innerCtrl"> <input ng-model="data" /> </div> <div ng-controller="innerCtrl"> <input ng-model="data" /> <button ng-click="reattach()">Reattach</button> </div> </div> </div> JSFiddle: http://jsfiddle.net/msfrisbie/r33nekbg/
It is also possible to directly access the parent $scope object using $scope.$parent and ignore the inheritance completely. This can be done as follows:
(app.js)
angular.module('myApp', [])
.controller('Ctrl', function() {});
(index.html)
<div ng-app="myApp">
<div ng-controller="Ctrl" ng-init="data=123"> <input ng-model="data" /> <div ng-controller="Ctrl"> <input ng-model="$parent.data" /> </div> <div ng-controller="Ctrl"> <input ng-model="$parent.data" /> </div> </div> </div>
Troublemaker built-in directives
The preceding examples explicitly demonstrate nested scopes that prototypically inherit from the parent $scope object. In a real application, this would likely be very easy to detect and debug. However, AngularJS comes bundled with a number of built-in directives that silently create their own scopes, and if prototypical scope inheritance is not heeded, this can cause problems. There are six built-in directives that create their own scope: ngController, ngInclude, ngView, ngRepeat, ngIf, and ngSwitch.
The following examples will interpolate the $scope $id into the template to demonstrate the creation of a new scope.
ngController
The use of ngController should be obvious, as your controller logic relies on attaching functions and data to the new child scope created by the ngController directive.
ngInclude
Irrespective of the HTML content of whatever is being included, ng-include will wrap it inside a new scope. As ng-include is normally used to insert monolithic application components that do not depend on their surroundings, it is less likely that you would run into the $scope inheritance problems using it.
The following is an incorrect solution:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope) { $scope.data = 123; }); (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> Scope id: {{ $id }}
<input ng-model="data " />
<ng-include src="'innerTemplate.html'"></ng-include> </div>
<script type="text/ng-template" id="innerTemplate.html"> <div>
Scope id: {{ $id }}
<input ng-model="data " /> </div>
The new scope inside the compiled ng-include directive inherits from the controller $scope, but binding to its primitive value sets up the same problem.
The following is the correct solution:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope) { $scope.data = { val: 123 }; }); (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> Scope id: {{ $id }}
<input ng-model="data.val" />
<ng-include src="'innerTemplate.html'"></ng-include> </div>
<script type="text/ng-template" id="innerTemplate.html"> <div>
Scope id: {{ $id }}
<input ng-model="data.val" /> </div>
</script> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/c8nLk676/
ngView
With respect to prototypal inheritance, ng-view operates identically to ng-include. The inserted compiled template is provided its own new child $scope, and correctly inheriting from the parent $scope can be accomplished in the exact same fashion.
ngRepeat
The ngRepeat directive is the most problematic directive when it comes to incorrectly managing the $scope inheritance. Each element that the repeater creates is given its own scope, and modifications to these child scopes (such as inline editing of data in a list) will not affect the original object if it is bound to primitives.
The following is an incorrect solution: (app.js) angular.module('myApp', []) .controller('Ctrl', function($scope) { $scope.names = [ 'Alshon Jeffrey', 'Brandon Marshall', 'Matt Forte', 'Martellus Bennett', 'Jay Cutler' ]; }); (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> Scope id: {{ $id }}
<pre>{{ names | json }}</pre> <div ng-repeat="name in names"> Scope id: {{ $id }}
<input ng-model="name" /> </div>
</div> </div>
As described earlier, changing the value of the input fields only serves to modify the instance of the primitive in the child scope, not the original object. One way to fix this is to restructure the data object so that instead of iterating through primitive types, it iterates through objects wrapping the primitive types.
The following is the correct solution:
(app.js)
angular.module('myApp', [])
.controller('Ctrl', function($scope) { $scope.players = [
{ name: 'Alshon Jeffrey' }, { name: 'Brandon Marshall' }, { name: 'Matt Forte' },
{ name: 'Martellus Bennett' }, { name: 'Jay Cutler' }
]; });
(index.html)
<div ng-app="myApp">
<div ng-controller="Ctrl"> Scope id: {{ $id }}
<pre>{{ players | json }}</pre> <div ng-repeat="player in players"> Scope id: {{ $id }}
<input ng-model="player.name" /> </div>
</div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/zesj1gb6/
With this, the original array is being modified properly, and all is right with the world. However, sometimes restructuring an object is not a feasible solution for an application. In this case, changing an array of strings to an array of objects seems like an odd workaround. Ideally, you would prefer to be able to iterate through the string array without modifying it first. Using track by as part of the ng-repeat expression, this is possible.
The following is also a correct solution:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope) { $scope.players = [ 'Alshon Jeffrey', 'Brandon Marshall', 'Matt Forte', 'Martellus Bennett', 'Jay Cutler' ]; }); (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl">
Scope id: {{ $id }}
<pre>{{ players | json }}</pre>
<div ng-repeat="player in players track by $index"> Scope id: {{ $id }}
<input ng-model="players[$index]" /> </div>
</div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/ovas398h/
Now, even though the repeater is iterating through the players array elements, as the child $scope objects created for each element will still prototypically inherit the players array, it simply binds to the respective element in the array using the $index repeater.
As primitive types are immutable in JavaScript, altering a primitive element in the array will replace it entirely. When this replacement occurs, as a vanilla utilization of ng-repeat identifies array elements by their string value, ng-repeat thinks a new element has been added, and the entire array will re-render—a functionality which is obviously undesirable for usability and performance reasons. The track by $index clause in the ng-repeat expression solves this problem by identifying array elements by their index rather than their string value, which prevents constant re-rendering.
ngIf
As the ng-if directive destroys the DOM content nested inside it every time its expression evaluates as false, it will re-inherit the parent $scope object every time the inner content is compiled. If anything inside the ng-if element directive inherits incorrectly from the parent $scope object, the child $scope data will be wiped out every time recompilation occurs. The following is an incorrect solution:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope) { $scope.data 123; $scope.show = false; }); (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl">
<input ng-model="data " />
<input type="checkbox" ng-model="show" /> <div ng-if="show">
Scope id: {{ $id }}
<input ng-model="data " /> </div>
</div> </div>
Every time the checkbox is toggled, the newly created child $scope object will re-inherit from the parent $scope object and wipe out the existing data. This is obviously undesirable in many scenarios. Instead, the simple utilization of one level of object indirection solves this problem. The following is the correct solution:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope) { $scope.data = { val: 123 }; $scope.show = false; }); (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> Scope id: {{ $id }}
<input ng-model="data.val" />
<input type="checkbox" ng-model="show" /> <div ng-if="show">
Scope id: {{ $id }}
<input ng-model="data.val" /> </div>
</div> </div>
ngSwitch
The ngSwitch directive acts much in the same way as if you were to combine several ngIf statements together. If anything inside the active ng-switch $scope inherits incorrectly from the parent $scope object, the child $scope data will be wiped out every time recompilation occurs when the watched switch value is altered.
The following is an incorrect solution:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope) { $scope.data = 123; }); (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> Scope id: {{ $id }}
<input ng-model="data " /> <div ng-switch on="data "> <div ng-switch-when="123"> Scope id: {{ $id }}
<input ng-model="data " /> </div>
<div ng-switch-default> Scope id: {{ $id }} Default
</div> </div> </div> </div>
In this example, when the outer <input> tag is set to the matching value 123, the inner <input> tag nested in ng-switch will inherit that value, as expected. However, when altering the inner input, it doesn't modify the inherited value as the prototypical inheritance chain is broken.
The following is the correct solution:
(app.js)
angular.module('myApp', [])
val: 123 }; }); (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> Scope id: {{ $id }}
<input ng-model="data.val" /> <div ng-switch on="data.val"> <div ng-switch-when="123"> Scope id: {{ $id }}
<input ng-model="data.val" /> </div>
<div ng-switch-default> Scope id: {{ $id }} Default </div> </div> </div> </div> JSFiddle: http://jsfiddle.net/msfrisbie/8kh41wdm/
Working with AngularJS forms
AngularJS offers close integration with HTML form elements in the form of directives to afford you the ability to build animated and styled form pages, complete with validation, quickly and easily.
How to do it…
AngularJS forms exist inside the <form> tag, which corresponds to a native AngularJS directive, as shown in the following code. The novalidate attribute instructs the browser to ignore its native form validation:
<form novalidate> <!-- form inputs --> </form>
Your HTML input elements will reside inside the <form> tags. Each instance of the <form> tag creates a FormController, which keeps track of all its controls and nested forms. The entire AngularJS form infrastructure is built on top of this.
As browsers don't allow nested form tags, ng-form
should be used to nest forms.
What the form offers you
Suppose you have a controller; a form in your application is as follows:
<div ng-controller="Ctrl">
<form novalidate name="myform">
<input name="myinput" ng-model="formdata.myinput" /> </form>
</div>
With this, Ctrl $scope is provided a constructor for the FormController as $scope.myform, which contains a lot of useful attributes and functions. The individual form entries for each input can be accessed as child FormController objects on the parent FormController object; for example, $scope.myform.myinput is the FormController object for the text input.
The inputs must be coupled with an ng-model directive for the state and validation bindings to work.
Tracking the form state
Inputs and forms are provided with their own controllers, and AngularJS tracks the state of both the individual inputs and the entire form using a pristine/dirty dichotomy. "Pristine" refers to the state in which inputs are set to their default values, and "dirty" refers to any modifying action taken on the model corresponding to the inputs. The "pristine" state of the entire form is a logical AND result of all the input pristine states or a NOR result of all the dirty states; by its inverted definition, the "dirty" state of the entire form represents an OR result of all the dirty states or a NAND result of all the pristine states.
JSFiddle: http://jsfiddle.net/msfrisbie/trjfzdwc/ These states can be used in several different ways.
Both the <form> and <input> elements have the CSS classes, ng-pristine and ng-dirty, automatically applied to them based on the state the form is in. These CSS classes can be used to style the inputs based on their state, as follows:
form.ng-pristine { } input.ng-pristine { } form.ng-dirty { } input.ng-dirty { }
All instances of the FormController and the ngModelController instances inside it have the $pristine and $dirty Boolean properties available. These can be used in the controller business logic or to control the user flow through the form.
The following example shows Enter a value until the input has been modified:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope) { $scope.$watch('myform.myinput.$pristine', function(newval) { $scope.isPristine = newval; }); }); (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl">
<form novalidate name="myform">
<input name="myinput" ng-model="formdata.myinput" /> </form> <div ng-show="isPristine"> Enter a value </div> </div> </div> JSFiddle: http://jsfiddle.net/msfrisbie/unxbyun2/
Alternately, as the form object is attached to the scope, it is possible to directly detect whether the input is pristine in the view:
(index.html)
<div ng-app="myApp">
<div ng-controller="Ctrl">
<form novalidate name="myform">
<input name="myinput" ng-model="formdata.myinput" /> <div ng-show="myform.myinput.$pristine">
Enter a value </div> </form> </div> </div> JSFiddle: http://jsfiddle.net/msfrisbie/pr3L1e2b/
It's also possible to force a form or input into a pristine or dirty state using the $setDirty() or $setPristine() methods. This has no bearing on what exists inside the inputs at that point in time; it simply overrides the Booleans values, $pristine and $dirty, and sets the corresponding CSS class, ng-pristine or ng-dirty. Invoking these methods will propagate to any parent forms.
Validating the form
Similar to the pristine/dirty dichotomy, AngularJS forms also have a valid/invalid dichotomy. Input fields in a form can be assigned validation rules that must be satisfied for the form to be valid. AngularJS tracks the validity of both the individual inputs and the entire form using