diff --git a/src/ng/directive/form.js b/src/ng/directive/form.js
index 06ffad868d0c..1a13adea79a2 100644
--- a/src/ng/directive/form.js
+++ b/src/ng/directive/form.js
@@ -46,8 +46,8 @@ var nullFormCtrl = {
*
*/
//asks for $scope to fool the BC controller module
-FormController.$inject = ['$element', '$attrs', '$scope'];
-function FormController(element, attrs) {
+FormController.$inject = ['$element', '$attrs', '$scope', '$animate'];
+function FormController(element, attrs, $scope, $animate) {
var form = this,
parentForm = element.parent().controller('form') || nullFormCtrl,
invalidCount = 0, // used to easily determine if we are valid
@@ -70,9 +70,8 @@ function FormController(element, attrs) {
// convenience method for easy toggling of classes
function toggleValidCss(isValid, validationErrorKey) {
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
- element.
- removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
- addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
+ $animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey);
+ $animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
}
/**
@@ -177,7 +176,8 @@ function FormController(element, attrs) {
* state (ng-dirty class). This method will also propagate to parent forms.
*/
form.$setDirty = function() {
- element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
+ $animate.removeClass(element, PRISTINE_CLASS);
+ $animate.addClass(element, DIRTY_CLASS);
form.$dirty = true;
form.$pristine = false;
parentForm.$setDirty();
@@ -199,7 +199,8 @@ function FormController(element, attrs) {
* saving or resetting it.
*/
form.$setPristine = function () {
- element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
+ $animate.removeClass(element, DIRTY_CLASS);
+ $animate.addClass(element, PRISTINE_CLASS);
form.$dirty = false;
form.$pristine = true;
forEach(controls, function(control) {
@@ -284,8 +285,28 @@ function FormController(element, attrs) {
* hitting enter in any of the input fields will trigger the click handler on the *first* button or
* input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
*
- * @param {string=} name Name of the form. If specified, the form controller will be published into
- * related scope, under this name.
+ * ## A note about animations with `ngForm`
+ *
+ * Animations in ngForm work with the pristine, dirty, invalid and valid events that are triggered when
+ * the values of form change. This system works like the animation system present with ngClass.
+ *
+ *
+ * //
+ * //a working example can be found at the bottom of this page
+ * //
+ * .my-element.ng-dirty-add {
+ * transition:0.5s linear all;
+ * background: red;
+ * }
+ * .my-element.ng-dirty {
+ * background: white;
+ * }
+ *
+ * .my-element.ng-dirty-add { ... }
+ * .my-element.ng-dirty-add.ng-dirty-add-active { ... }
+ * .my-element.ng-dirty-remove { ... }
+ * .my-element.ng-dirty-remove.ng-dirty-remove-active { ... }
+ *
*
* @example
@@ -295,6 +316,16 @@ function FormController(element, attrs) {
$scope.userType = 'guest';
}
+
+ *
+ * @param {string=} name Name of the form. If specified, the form controller will be published into
+ * related scope, under this name.
+ *
+ * @animations
+ * removeClass .ng-dirty and addClass .ng-pristine: happens just after form became pristine
+ * removeClass .ng-pristine and addClass .ng-dirty: happens just after form became dirty
+ * removeClass .ng-invalid and addClass .ng-valid: happens just after form became valid
+ * removeClass .ng-valid and addClass .ng-invalid: happens just after form became invalid
*/
var formDirectiveFactory = function(isNgForm) {
return ['$timeout', function($timeout) {
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 4e77397878dd..724c974fe74f 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -915,8 +915,8 @@ var VALID_CLASS = 'ng-valid',
*
*
*/
-var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
- function($scope, $exceptionHandler, $attr, $element, $parse) {
+var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate',
+ function($scope, $exceptionHandler, $attr, $element, $parse, $animate) {
this.$viewValue = Number.NaN;
this.$modelValue = Number.NaN;
this.$parsers = [];
@@ -978,9 +978,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
// convenience method for easy toggling of classes
function toggleValidCss(isValid, validationErrorKey) {
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
- $element.
- removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
- addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
+ $animate.removeClass($element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey);
+ $animate.addClass($element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
}
/**
@@ -1041,7 +1040,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
this.$setPristine = function () {
this.$dirty = false;
this.$pristine = true;
- $element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
+ $animate.removeClass($element, DIRTY_CLASS);
+ $animate.addClass($element, PRISTINE_CLASS);
};
/**
@@ -1073,7 +1073,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
if (this.$pristine) {
this.$dirty = true;
this.$pristine = false;
- $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
+ $animate.removeClass($element, PRISTINE_CLASS);
+ $animate.addClass($element, DIRTY_CLASS);
parentForm.$setDirty();
}
@@ -1139,7 +1140,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* require.
* - Providing validation behavior (i.e. required, number, email, url).
* - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors).
- * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`).
+ * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`) including animations.
* - Registering the control with its parent {@link ng.directive:form form}.
*
* Note: `ngModel` will try to bind to the property given by evaluating the expression on the
@@ -1162,6 +1163,60 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* - {@link ng.directive:select select}
* - {@link ng.directive:textarea textarea}
*
+ * ## A note about animations with `NgModel`
+ *
+ * Animations work with the pristine, dirty, invalid and valid events that are triggered when
+ * the values of input change. This system works like the animation system present with ngClass.
+ *
+ *
+ * //
+ * //a working example can be found at the bottom of this page
+ * //
+ * .my-element.ng-dirty-add {
+ * transition:0.5s linear all;
+ * background: red;
+ * }
+ * .my-element.ng-dirty {
+ * background: white;
+ * }
+ *
+ * .my-element.ng-dirty-add { ... }
+ * .my-element.ng-dirty-add.ng-dirty-add-active { ... }
+ * .my-element.ng-dirty-remove { ... }
+ * .my-element.ng-dirty-remove.ng-dirty-remove-active { ... }
+ *
+ *
+ * @animations
+ * removeClass .ng-dirty and addClass .ng-pristine: happens just after input became pristine
+ * removeClass .ng-pristine and addClass .ng-dirty: happens just after input became dirty
+ * removeClass .ng-invalid and addClass .ng-valid: happens just after input became valid
+ * removeClass .ng-valid and addClass .ng-invalid: happens just after input became invalid
+ *
+ * @example
+ *
+
+
+
+ Update input to see transitions when valid/invalid.
+ Integer is a valid value.
+
+
+ *
*/
var ngModelDirective = function() {
return {
diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js
index 64ed302981a9..8a65f74079a7 100644
--- a/src/ngAnimate/animate.js
+++ b/src/ngAnimate/animate.js
@@ -32,6 +32,8 @@
* | {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave |
* | {@link ng.directive:ngClass#usage_animations ngClass} | add and remove |
* | {@link ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) |
+ * | {@link ng.directive:form#usage_animations form} | dirty, pristine, valid and invalid |
+ * | {@link ng.directive:ngModel#usage_animations ngModel} | dirty, pristine, valid and invalid |
*
* You can find out more information about animations upon visiting each directive page.
*
diff --git a/test/ng/directive/formSpec.js b/test/ng/directive/formSpec.js
index dde6f0a026c8..97c148f380cb 100644
--- a/test/ng/directive/formSpec.js
+++ b/test/ng/directive/formSpec.js
@@ -594,3 +594,109 @@ describe('form', function() {
});
});
});
+
+describe('form animations', function() {
+ var body, element, $rootElement, changeInputValue;
+
+ function html(html) {
+ body.append($rootElement);
+ $rootElement.html(html);
+ element = $rootElement.children().eq(0);
+ return element;
+ }
+
+ beforeEach(function() {
+ // we need to run animation on attached elements;
+ body = jqLite(document.body);
+ });
+
+ afterEach(function(){
+ dealoc(body);
+ dealoc(element);
+ body.removeAttr('ng-animation-running');
+ });
+
+ beforeEach(module('mock.animate'));
+
+ beforeEach(module(function($animateProvider, $provide) {
+ return function(_$rootElement_) {
+ $rootElement = _$rootElement_;
+ };
+ }));
+
+ describe('dirty/pristine/valid/invalid states', function() {
+ it('should fire animations', inject(function($compile, $rootScope, $animate) {
+ var flushAnimates = function() {
+ $animate.flushNext('removeClass');
+ $animate.flushNext('addClass');
+ };
+
+ var $scope = $rootScope.$new();
+ $scope.name = "bad value";
+ element = $compile(html(
+ ''
+ ))($scope);
+
+ $scope.$digest();
+
+ var form = element,
+ formCtrl = $scope.testForm,
+ input = form.find('input').eq(0),
+ inputCtrl = input.controller('ngModel');
+
+ expect($scope.name).toBe('bad value');
+ expect(input).toBePristine();
+ expect(form).toBePristine();
+
+ flushAnimates();
+ expect(form).toBeValid();
+
+ flushAnimates();
+ expect(input).toBeValid();
+
+ flushAnimates();
+ expect(input).toBeInvalid();
+
+ flushAnimates();
+ expect(input).toHaveClass('ng-invalid-pattern');
+
+ flushAnimates();
+ expect(form).toBeInvalid();
+
+ flushAnimates();
+ expect(form).toHaveClass('ng-invalid-pattern');
+
+ inputCtrl.$setViewValue('x');
+ $scope.$digest();
+ expect($scope.name).toBe('x');
+
+ flushAnimates();
+ expect(input).toBeDirty();
+
+ flushAnimates();
+ expect(form).toBeDirty();
+
+ flushAnimates();
+ expect(input).toBeValid();
+
+ flushAnimates();
+ expect(input).not.toHaveClass('ng-invalid-pattern');
+
+ flushAnimates();
+ expect(form).toBeValid();
+
+ flushAnimates();
+ expect(form).not.toHaveClass('ng-invalid-pattern');
+
+ formCtrl.$setPristine();
+
+ flushAnimates();
+ expect(form).toBePristine();
+
+ flushAnimates();
+ expect(input).toBePristine();
+ }));
+ });
+});