2

Is there any difference in performance when binding to simple fields (e.g. [(ngModel)]="room") in comparison to binding to properties of objects? (e.g. [(ngModel)]="lesson.room") If yes, why is that?

Concerning getters: Am I correct in my understanding that binding to getters/setters and e.g. one-way binding to functions is bad, because the function will be called every single detection cycle, without Angular being able to differentiate between situations where there was no change and situations where a change actually happened and an update of the view is in fact necessary? This should make getters the least performant option in this regard.

fscheidl
  • 2,281
  • 3
  • 19
  • 33

2 Answers2

5

There should be no difference in performance, at least until lesson reference is changed. Even then the difference will be nonexistent because property access is very fast in JavaScript. It should be noticed that room is a property too.

If accessor method or property getter is bound, it will be called on every change detection cycle. It may be appropriate if function call isn't costly and/or change detection cycles aren't too dense. If they are dense, their amount can be reduced with OnPush change detection strategy.

For example, {{ foobar }} binding is ok:

get foobar() {
  return this.foo + this.bar;
}

And {{ factorial(num) }} binding may be costly, it can be refactored to pure pipe or be cached to a property on num change.

It should be noticed that there may be some caveats, e.g. property getter will suffer performance penalty in Chrome/V8 if it isn't defined on a prototype, like:

lesson = { get room() {/*...*/} }

Depending on real performance impact, these concerns may be considered preliminary optimization.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • You are talking about "dense" change detection cycles. Is there any way of influencing the density without changing the change detection strategy (or detaching the change detector)? As far as my understanding goes, Angular decides when a change detection is run: After x seconds, when typing, clicking etc. – fscheidl Jan 30 '18 at 11:14
  • 1
    Change detection cycle happens when change detection it is triggered in current zone. Yes, it will be triggered by DOM or other async events (as long as they happen in a zone). Pieces of code that are responsible for CD spam can be throttled/debounced or evaluated with zone `runOutsideAngular`. CD can also be (temporarily) switched off with ChangeDetectorRef `detach`. – Estus Flask Jan 30 '18 at 11:26
  • 1
    Notice that Angular event bindings trigger CD, e.g. `(mousemove)=...` is a notable spammer. So it should be either be treated with `detach`, or event listener has to be set up manually and throttled, like `Observable.fromEvent(...).debounceTime(...)`. Again, this is preliminary optimization, unless proven otherwise. – Estus Flask Jan 30 '18 at 11:56
  • I am using `@HostListener('document:keydown', ['$event'])` and noticed this is probably the culprit of my performance issues. Apparently triggering change detection from HostListener events is neither affected by detaching the change detector nor by setting the detection strategy to `OnPush` for the relevant component. Any hint you can give me on (temporarily) avoiding HostListener events triggering change detection? – fscheidl Feb 01 '18 at 09:18
  • 1
    I see. Yes, HostListener will likely be a problem. It's same as suggested above, you have to set up a listener manually with `Observable.fromEvent(document, 'keydown')` and debounce it or disable CD there with runOutsideAngular. `document` is available for DI as DOCUMENT provider. – Estus Flask Feb 01 '18 at 09:34
1

There is a slight difference between

[(ngModel)]="room"

and

[(ngModel)]="lesson.room")

because in the former case change detection checks only if room is identical to the instance when it last checked and in the later case it has to check lession and then room.

If lession is an object and room a normal property this won't really matter because such checks are extremely fast.

Function invocation is less performant than simple object idendity comparison, but it's not too bad. It depends how often change detection happens. The most important parts are

  • that the function returns the same value on subsequent calls (if there was no async execution in between) otherwise Angular recognizes this and throws the infamous "The expression has changed since it was last checked"

  • that the function doesn't do expensive work by itself.

If these criterias are considered binding to functions is acceptable in some situations. It's just prone to get it wrong and therefore it's usually better to not use it until you fully understand how change detection works.

The best way to find out if it works for you is to create your own benchmarks for your concrete use case.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • You are saying "it depends how often change detection happens". The number of change detection cycles cannot be influenced without changing the change detection cycle (or detaching the change detector), can it? So if I understand it correctly, the overhead of using getters/functions is the overhead of the function call itself plus any work/calculation the function does? This overhead should then be relevant for every single detection cycle. – fscheidl Jan 30 '18 at 11:17
  • "without changing the change detection cycle" should be "without changing the change detection strategy" – fscheidl Jan 30 '18 at 11:23
  • 1
    If there is no change propagated to a child component, then there won't be a change. For example you have `@Input() a:string; b:string;` and `` with `ChangeDetectionStrategy.OnPush` in the `child` component, then change detection in the child component will only run if you change `b`. If you change the input in the parent to ``@Input() set a(value:string) { if(value.prop this.b.prop) { this.b = value; this.cdRef.detectChanges(); };` then change detection on `` will only run when `value.prop differs from `this.b.prop` (or other events that cause change ... – Günter Zöchbauer Jan 30 '18 at 11:23
  • 1
    ... detection in components with `OnPush`. – Günter Zöchbauer Jan 30 '18 at 11:23
  • 2
    So what's the problem with changing the change detection strategy? – Günter Zöchbauer Jan 30 '18 at 11:24
  • There is no problem, just asking out of curiosity and understanding. So did I understand the overhead issue correctly? Using a simple field, Angular carries out an identity comparison every cycle, using a getter, the function is called every cycle (function call overhead) and the return value is then compared to the previous value analogously to simple fields (i.e. identity comparison), correct? – fscheidl Jan 30 '18 at 11:27