Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

perf(input): prevent multiple $digest when input is blurred #8450

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -2182,6 +2182,8 @@ var ngModelDirective = function() {
}

element.on('blur', function(ev) {
if (modelCtrl.$touched) return;

scope.$apply(function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not $evalAsync or $timeout instead?

Using $apply, at least in our directive is causing Error: [$rootScope:inprog] $apply already in progress since we are setting focus to other input during a current $apply which will at the same time make this blur event run and cause the error.

Here's plunker with the problem

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recommended way to execute an expression in Angular from outside angular realm (DOM events) is to use scope.$apply.

The error in your directive comes from line 244 when the close method, which is executed inside the angular realm (a $digest cycle), calls a DOM method (focus()). Any other directive that binds to the focus DOM event and attempts to execute an expression in the angular realm using $apply will cause an $apply already in progress exception.

modelCtrl.$setTouched();
});
Expand Down
17 changes: 17 additions & 0 deletions test/ng/directive/inputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,23 @@ describe('ngModel', function() {
dealoc(element);
}));

it('should not cause a digest on "blur" event if control is already touched',
inject(function($compile, $rootScope) {

var element = $compile('<form name="myForm">' +
'<input name="myControl" ng-model="value" >' +
'</form>')($rootScope);
var inputElm = element.find('input');
var control = $rootScope.myForm.myControl;

control.$setTouched();
spyOn($rootScope, '$apply');
browserTrigger(inputElm, 'blur');

expect($rootScope.$apply).not.toHaveBeenCalled();

dealoc(element);
}));

it('should register/deregister a nested ngModel with parent form when entering or leaving DOM',
inject(function($compile, $rootScope) {
Expand Down