-
Notifications
You must be signed in to change notification settings - Fork 27.4k
perf(ngClass): optimize the case of static map of classes of large objects #14394
Conversation
Why aren't we always using This was probably implemented before |
Is the first thing that I tried, just in case that it worked well with strings too, but then is when I discovered (some test failed) that the follow array form is now valid: <div ng-class="['some', 'thing', {else: else}]">...</div>
And in this case There is another option: make the watching function to convert always the input to a string, it should be simple, fast (at least as fast as now), it might simplify some cases, but it might require a quite refactor. I'm not sure if it is necessary, because all cases that I have found of large copy structures were always written with ng-class="{...}" form. But, if you consider so, I can try the rewrite. |
Uhmmm may be is not so complicated, I think that there is a short cut, I'll try. |
I forgot we support the object-inside-array syntax. Maybe we should special-case the |
I'm afraid that's not possible, because you can do: <div ng-init="foo = ['some', 'thing', {else: else}]"></div>
<div ng-class="foo">
..
</div> And in such case it is not compliant the use of $watchCollection. Anyway, I have an alternative solution, I'm just cleaning up it a little bit, and I'll push in a separated commit, so you can choose which one you prefer. (I think that the second is better, the first is the one that I had in my head for a long time). |
Done. Now it compares always strings and the watch computes classes. I think that the second one is better, If you want I can:
|
In #14394 (comment) I meant use Could you put your second commit on a separate PR, remove the unused |
About #14394 (comment) the problem is that the detection of which kind of watch is applied is being doing at compile time, but knowing that there is an array, a string, or an object is something that has to be resolved un runtime. The binded value can be a variable containing either an array or an object. About second PR, great! I had this in mind, better two branches so it is easy to compare performances. And thanks for noticing that I left an unused constant. About "::{" expressions, I did the check the first time and the RegEx, but it turns out that a the value never gets copied because as soon as it has a value the watcher is removed. Anyway I add the test case. |
Uhmmm I have put a console.log inside copy and it copies verylargeobject just once :/ but it is not stored into last. I'm not sure how to test that it does not copy verylargeobject, ¿any idea? |
Off the top of my head, I would try putting a get accessor property on var verylargeobject = {};
Object.defineProperty(verylargeobject, 'prop', {
get: jasmine.createSpy('getProp')
}); |
yep! thanks! I'll try it. |
Part 1 done. |
Part 2 done: the alternative is here: #14404 |
Part 3 done: the bug that I spotted in the plnkr http://plnkr.co/edit/f0V9RVDGREetSW00TTRZ?p=preview is tested and solved here: #14405 (it is applied here, but #14404 also solves it in the refactor) One question about this undocumented behaviour: it('should support mixed array/object variable with a mutating object', inject(function($rootScope, $compile) {
$rootScope.classVar = ['red', ['green', {blue: true}]];
element = $compile('<div class="existing" ng-class="classVar"></div>')($rootScope);
$rootScope.$digest();
expect(element.hasClass('red')).toBeTruthy();
expect(element.hasClass('green')).toBeTruthy();
expect(element.hasClass('blue')).toBeTruthy();
})); because it it works in 1.5.3 buggy (even with the fix) the following test fails: it('should support mixed nested array and object leafs variable with mutating object', inject(function($rootScope, $compile) {
$rootScope.classVar = ['red', ['green', {blue: true}]];
element = $compile('<div class="existing" ng-class="classVar"></div>')($rootScope);
$rootScope.$digest();
$rootScope.classVar[1][1].blue = false;
expect(element.hasClass('red')).toBeTruthy();
expect(element.hasClass('green')).toBeTruthy();
expect(element.hasClass('blue')).toBeFalsy();
})); So, ¿Is it a bug to
In #14404 works 100% well with multiple nested arrays. |
Closing this in favor of #14404. |
What kind of change does this PR introduce? (Bug fix, feature, docs update, ...): performance optimization of the ngClass
What is the current behavior? (You can also link to an open issue here): when there is an ngClass with an object, it makes a deep copy of all its values, although it only needs to check for truly or falsy. It is because it uses
$watch(,, true)
.What is the new behavior (if this is a feature change)?: it tries to statically detect the case of
ng-class="{foo: bar, ...}"
case and then do a$watchCollection(,)
instead of a$watch(,, true)
. Its a mere and simple test against initial attribute value.Does this PR introduce a breaking change?: nop
Please check if the PR fulfills these requirements
Other information:
Time to time, working with my customers, they ask to me to make performance tasks: they give me access to angular projects and they ask me to optimize them. One of the mistakes that I found many times is to do the following:
because
user.friends
is truly (it has a value) or falsy (it is undefined or null) it works, but some times with truly values, associated data can be big taking up to seconds perform some copy (imagine that friends are not ids but objects with more relationships, ...), in such cases, always give white papers asking for things like:which solves all performance problems, or if it is only one class do something like:
which gives a string easy to copy and compare (but not so recommendable because not all designers or programmers understand it easily and difficult to express more than one class)
The current solution that I give is simple, fast to compute, and it works in 99% of the cases. I can try to do a custom changeDetector (like the one in $watchCollection) but I believe that it is not worth (I didn't know that arrays could be used to add classes). So it will work well in the initial example:
but: