<!DOCTYPE html> <html> <head> <script src="angular.js"></script> <script src="script.js"></script> <style> .ng-pristine { border: solid green 3px; } .ng-dirty { border: solid orange 3px; } form { padding: 10px; } </style> </head> <body ng-app="mainModule"> <div ng-controller="mainController"> <h3>1. Tracking changes on single elements</h3> <label>Text: <input type="text" ng-model="textValue" ng-change="onTextChange()" /></label><br /> <strong>Value:</strong> {{textValue}}<br /> <br /> <strong>Changed:</strong> {{isTextChanged}} ({{textChangesCounter}} times)<br /> <br /> <br /> <label>E-mail: <input type="email" ng-model="emailValue" ng-change="onEmailChange()" /></label><br /> <strong>Value:</strong> {{emailValue}}<br /> <br /> <strong>Changed:</strong> {{isEmailChanged}} ({{emailChangesCounter}} times)<br /> <br /> <h3>2. Tracking changes in a form</h3> <form name="testForm"> <label>Text: <input type="text" name="formText" ng-model="formTextValue" /></label> <strong>State:</strong> {{getItemState(testForm.formText)}}<br /> <label>E-mail: <input type="email" name="formEmail" ng-model="formEmailValue" /></label> <strong>State:</strong> {{getItemState(testForm.formEmail)}}<br /> <br /> <strong>Form state:</strong> {{getItemState(testForm)}} </form> </div> </body> </html>
angular.module("mainModule", []) .controller("mainController", function ($scope) { $scope.isTextChanged = false; $scope.isEmailChanged = false; $scope.textChangesCounter = 0; $scope.emailChangesCounter = 0; $scope.onTextChange = function () { $scope.isTextChanged = true; $scope.textChangesCounter++; }; $scope.onEmailChange = function () { $scope.isEmailChanged = true; $scope.emailChangesCounter++; }; $scope.getItemState = function (item) { if (item.$pristine) { return "pristine"; } else if (item.$dirty) { return "dirty"; } else { return ""; } }; });



When you load an HTML template managed by AngularJS, all the input elements with an ng-model directive also have the ng-pristine CSS class associated to them. This means that the content of the element has not changed since the time it has been made available to the user in the browser interface. When the user modifies the content by typing into the control, the ng-pristine CSS class is removed from it and replaced by ng-dirty. In this way, you can set different CSS styles to visually track the pristine and dirty states of each input element. It's important to keep in mind that the CSS styles are applied only if the control is associated to a model variable through the ng-model directive.

In every input element we can also bind an handler function through the ng-change directive to listen to every change event. Every time the model variable is update as a consequence of a user interaction, the handler function is called. There are two important things to note here:

  • the handler is called only if the model variable changes as a consequence of a user interaction with the control and not if the variable changes because a new value has been assigned to it programmatically
  • the handler is called only if the model variable changes, this means that if the text in the input element is not valid (e.g. it's not a valid e-mail address in an input with type email), the handler will not be called until the text becomes valid again so it can be assigned to the model variable

Point 1 of the example shows how we can change the aspect of the input controls by simply assigning some styling attributes to the ng-pristine and ng-dirty CSS classes. We also see how many times the change handlers specified through the ng-change directive are called. Note the difference between the text input and the email input. In the second one, the change handler is called only when the e-mail address typed in the control is considered valid.

Point 2 shows how we can keep track of the global state of a form and how we can programmatically check the state of the single input elements through their parent form. To do this, we need to assign a name to the form and to all the input elements whose state needs to be accessed programmatically. As you can see, the state of the form can be accessed through the testForm variable that is automatically defined on the scope by AngularJS. Any input element inside the form that has also an associated name, is made available for a programmatical access through the testForm variable, so if we need to access the state of formText, all we need to do in the code is write $scope.testForm.formText. The state of a single element can be decoded by reading the value of two variables: $pristine and $dirty. This is exactly what the getItemState function does in the example.