Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bbb5151

Browse files
committedSep 25, 2014
fix(ngModel): minimize jank when toggling CSS classes during validation
Previously, class toggling would always occur immediately. This causes problems in cases where class changes happen super frequently, and can result in flickering in some browsers which do not handle this jank well. Closes #8234
1 parent b9e899c commit bbb5151

File tree

2 files changed

+94
-1
lines changed

2 files changed

+94
-1
lines changed
 

‎src/ng/directive/input.js

+49-1
Original file line numberDiff line numberDiff line change
@@ -2053,9 +2053,11 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
20532053
};
20542054

20552055
this.$$parseAndValidate = function() {
2056+
var pendingClassChanges = null;
20562057
var viewValue = ctrl.$$lastCommittedViewValue;
20572058
var modelValue = viewValue;
20582059
var parserValid = isUndefined(modelValue) ? undefined : true;
2060+
var flushPendingClassChanges = schedulePendingClassChanges($scope, ctrl, $element, $animate);
20592061

20602062
if (parserValid) {
20612063
for(var i = 0; i < ctrl.$parsers.length; i++) {
@@ -2092,6 +2094,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
20922094
ctrl.$$writeModelToScope();
20932095
}
20942096
}
2097+
2098+
flushPendingClassChanges();
20952099
};
20962100

20972101
this.$$writeModelToScope = function() {
@@ -2197,7 +2201,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
21972201
// TODO(perf): why not move this to the action fn?
21982202
if (modelValue !== ctrl.$modelValue) {
21992203
ctrl.$modelValue = modelValue;
2200-
2204+
var flushPendingClassChanges = schedulePendingClassChanges($scope, ctrl, $element, $animate);
22012205
var formatters = ctrl.$formatters,
22022206
idx = formatters.length;
22032207

@@ -2211,13 +2215,45 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
22112215

22122216
ctrl.$$runValidators(undefined, modelValue, viewValue, noop);
22132217
}
2218+
flushPendingClassChanges();
22142219
}
22152220

22162221
return modelValue;
22172222
});
22182223
}];
22192224

22202225

2226+
function schedulePendingClassChanges(scope, ctrl, element, animate) {
2227+
if (!ctrl.$$pendingClassChanges) {
2228+
ctrl.$$pendingClassChanges = {};
2229+
2230+
if (scope.$$phase || scope.$root.$$phase) {
2231+
scope.$$postDigest(flushPendingClassChangesImmediately);
2232+
} else {
2233+
return flushPendingClassChangesImmediately;
2234+
}
2235+
}
2236+
return noop;
2237+
2238+
function flushPendingClassChangesImmediately() {
2239+
flushPendingClassChanges(animate, element, ctrl.$$pendingClassChanges);
2240+
ctrl.$$pendingClassChanges = null;
2241+
}
2242+
}
2243+
2244+
2245+
function flushPendingClassChanges($animate, element, pendingChanges) {
2246+
var keys = Object.keys(pendingChanges);
2247+
2248+
for (var i=0, ii = keys.length; i < ii; ++i) {
2249+
var key = keys[i];
2250+
var value = pendingChanges[key];
2251+
if (value < 0) $animate.removeClass(element, key);
2252+
else if (value > 0) $animate.addClass(element, key);
2253+
}
2254+
}
2255+
2256+
22212257
/**
22222258
* @ngdoc directive
22232259
* @name ngModel
@@ -3037,6 +3073,18 @@ function addSetValidityMethod(context) {
30373073
}
30383074

30393075
function cachedToggleClass(className, switchValue) {
3076+
var pendingChanges = ctrl.$$pendingClassChanges;
3077+
if (pendingChanges) {
3078+
if (switchValue) {
3079+
pendingChanges[className] = 1;
3080+
classCache[className] = true;
3081+
} else {
3082+
pendingChanges[className] = -1;
3083+
classCache[className] = false;
3084+
}
3085+
return;
3086+
}
3087+
30403088
if (switchValue && !classCache[className]) {
30413089
$animate.addClass($element, className);
30423090
classCache[className] = true;

‎test/ng/directive/inputSpec.js

+45
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,51 @@ describe('NgModelController', function() {
892892
dealoc(element);
893893
}));
894894

895+
896+
it('should minimize janky setting of classes during $validate() and ngModelWatch', inject(function($animate, $compile, $rootScope) {
897+
var addClass = $animate.addClass;
898+
var removeClass = $animate.removeClass;
899+
var addClassCallCount = 0;
900+
var removeClassCallCount = 0;
901+
var input;
902+
$animate.addClass = function(element, className) {
903+
// Don't worry about classes that the input already has.
904+
if (input && element[0] === input[0] && (' ' + element.attr('class') + ' ').indexOf(' ' + className + ' ') < 0) {
905+
++addClassCallCount;
906+
}
907+
return addClass.call($animate, element, className);
908+
};
909+
910+
$animate.removeClass = function(element, className) {
911+
// Don't worry about classes that the input doesn't have.
912+
if (input && element[0] === input[0] && (' ' + element.attr('class') + ' ').indexOf(' ' + className + ' ') !== -1) {
913+
++removeClassCallCount;
914+
}
915+
return removeClass.call($animate, element, className);
916+
};
917+
918+
dealoc(element);
919+
920+
$rootScope.value = "123456789";
921+
element = $compile(
922+
'<form name="form">' +
923+
'<input type="text" ng-model="value" name="alias" ng-maxlength="10">' +
924+
'</form>'
925+
)($rootScope);
926+
927+
var form = $rootScope.form;
928+
input = element.children().eq(0);
929+
930+
$rootScope.$digest();
931+
932+
expect(input).toBeValid();
933+
expect(input).not.toHaveClass('ng-invalid-maxlength');
934+
expect(input).toHaveClass('ng-valid-maxlength');
935+
expect(addClassCallCount).toBe(2);
936+
expect(removeClassCallCount).toBe(0);
937+
938+
dealoc(element);
939+
}));
895940
});
896941
});
897942

0 commit comments

Comments
 (0)
This repository has been archived.