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

fix(ngModel): minimize jank when toggling CSS classes during validation #9263

Closed
wants to merge 1 commit into from

Conversation

caitp
Copy link
Contributor

@caitp caitp commented Sep 25, 2014

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.

/cc @matsko please help review this! It is very much your area of expertise!

Closes #8234

@caitp
Copy link
Contributor Author

caitp commented Sep 25, 2014

@jeffbcross / @tbosch also it would be very good if you could take a look and see if this is the way we want to minimize the jank, or if there are any other places you think might benefit from it

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 angular#8234
@caitp
Copy link
Contributor Author

caitp commented Sep 25, 2014

http://plnkr.co/edit/d2qK45maTpcPTkU64ewI?p=preview here's a fork of the plunker from the original issue, which when tested on SL with IE11, seems to indicate that this fix does what we want.

I was able to reproduce with the beta 15 plunker pretty quickly (using both the ng-if button and the ng-repeat button), (although the ng-if button took a few more tries to see the blinking), but after a few minutes of trying, the forked plunker would not display any of the blinking.

@caitp
Copy link
Contributor Author

caitp commented Sep 25, 2014

asked some volunteers to test with whatever version of IE they had handy on their windows machines, and it sounds like this does the right thing

@tbosch
Copy link
Contributor

tbosch commented Sep 25, 2014

I am not sure whether this fixes the right problem: Adding and removing classes should not be a problem for flashing as long as it is done in sync and no one causes a rendering. Could you investigate a little bit more?

@caitp
Copy link
Contributor Author

caitp commented Sep 25, 2014

As far as I'm aware, we are not asynchronously changing classes. Certain CSS properties are known to cause relayout when used/changed (box-shadow may be one of these, which is used in the plunker). Non-IE browsers don't suffer from this jank, so it's possible that IE is simply re-rendering immediately when the attribute is changed. The jank just doesn't affect other browsers, and I don't have a native windows machine so it's a bit hard to debug effectively. However, the fix does prevent it

I'm not able to reproduce with a trivial example, but I think we can likely see some performance benefits if we avoid switching the CSS classes back and forth so much, regardless

@caitp
Copy link
Contributor Author

caitp commented Sep 25, 2014

http://jsbin.com/butusalilifi/1/edit I can't reproduce it with a trivial reproduction, but as I posted in the issue itself, some classes are changing back and forth a lot more than the "once for each control" that should be happening. It's possible that the amount of back-and-forth switching in a single turn has an effect on this. But I don't believe we're deferring anything to a different turn, since ngAnimate isn't being used.

@caitp
Copy link
Contributor Author

caitp commented Sep 25, 2014

screen shot 2014-09-25 at 3 16 51 pm

It looks to me like the node gets created, inserted, and runs its first set of validation functions --- but in the same digest, ngModelWatch happens and re-validates it, and then the maxlength attribute is observed and validators run yet again. Somehow, IE is jankier than other browsers with this

So, it's hard to reproduce this exactly in a minimal scenario, but with this fix, class changes from those three validation runs are combined into one set of changes. (there may be a slight perf increase this way, since we're switching into native code less frequently --- just the slight cost of constructing a new object to throw away for each model that gets processed)

@caitp
Copy link
Contributor Author

caitp commented Sep 25, 2014

Anyways, having researched this a bit, I think this is the best solution --- we can't really defer validation, otherwise values will not be what people expect --- but we can defer updating CSS classes. The only improvement that I can think of would be reusing the pendingClassChanges object instead of throwing it away every digest. But at least it gets reused by all of the different validator runs that happen within the digest.

@tbosch
Copy link
Contributor

tbosch commented Sep 25, 2014

@caitp
ngAnimate already has the behavior of buffering all changes to classes of elements until the end of the digest via $$postDigest. How about we also do this in the no-op ngAnimate module?

This would be a more general solution that would also work for other cases...

@tbosch
Copy link
Contributor

tbosch commented Sep 25, 2014

Same is true for enter and leave of $animate. I really think that there should be no different in the behavior when including ngAnimate compared to not including it!

@caitp
Copy link
Contributor Author

caitp commented Sep 25, 2014

I will look at refactoring it to do that

@tbosch
Copy link
Contributor

tbosch commented Sep 25, 2014

Thanks!

@caitp
Copy link
Contributor Author

caitp commented Sep 25, 2014

I've looked into it, I think there are some things that suck about this --- here we can use the local scope, but we can't really force ngAnimate / $animate to do that. This is great for ng-click or whatever but really sucks for anyone that wants to use local digests.

caitp added a commit to caitp/angular.js that referenced this pull request Sep 25, 2014
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

Closes angular#8234
Closes angular#9263
@tbosch
Copy link
Contributor

tbosch commented Sep 30, 2014

We really should have $animate behave the same way whether someone includes ngAnimate or not. So we should not make the change only specifically to ngModel. If someone needs special handling for local digests we can add logic later on... Closing this in favor of #9283.

@tbosch tbosch closed this Sep 30, 2014
caitp added a commit to caitp/angular.js that referenced this pull request Sep 30, 2014
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

Closes angular#8234
Closes angular#9263
caitp added a commit to caitp/angular.js that referenced this pull request Sep 30, 2014
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

Closes angular#8234
Closes angular#9263
caitp added a commit to caitp/angular.js that referenced this pull request Sep 30, 2014
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

Closes angular#8234
Closes angular#9263
caitp added a commit to caitp/angular.js that referenced this pull request Sep 30, 2014
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

Closes angular#8234
Closes angular#9263
caitp added a commit to caitp/angular.js that referenced this pull request Oct 2, 2014
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

Closes angular#8234
Closes angular#9263
caitp added a commit to caitp/angular.js that referenced this pull request Oct 3, 2014
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

Closes angular#8234
Closes angular#9263
caitp added a commit to caitp/angular.js that referenced this pull request Oct 8, 2014
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

Closes angular#8234
Closes angular#9263
caitp added a commit to caitp/angular.js that referenced this pull request Oct 8, 2014
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

Closes angular#8234
Closes angular#9263
caitp added a commit that referenced this pull request Oct 8, 2014
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

BREAKING CHANGE:

The $animate class API will always defer changes until the end of the next digest. This allows ngAnimate
to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than
many. This prevents jank in browsers such as IE, and is generally a good thing.

If you're finding that your classes are not being immediately applied, be sure to invoke $digest().

Closes #8234
Closes #9263
bullgare pushed a commit to bullgare/angular.js that referenced this pull request Oct 9, 2014
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

BREAKING CHANGE:

The $animate class API will always defer changes until the end of the next digest. This allows ngAnimate
to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than
many. This prevents jank in browsers such as IE, and is generally a good thing.

If you're finding that your classes are not being immediately applied, be sure to invoke $digest().

Closes angular#8234
Closes angular#9263
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

maxlength issue in 1.3.0 beta 15 (invalid -> valid)
3 participants