My goal is to create a series of versatile components (angular 1.5) with various optional bindings that can be utilized in multiple applications.
I am concerned about the potential creation of unnecessary watchers for an application that does not utilize most of the optional bindings.
For example:
Component declaration:
let dateRangeComponent = {
bindings: {
label: '@',
name1: '@',
name2: '@',
model1: '>?',
model2: '>?',
extra1: '>?'
},
template: `<div ng-if="$ctrl.model1>stuff</div>
<div ng-if="$ctrl.model2>stuff</div>
<div ng-if="$ctrl.extra1>stuff</div>`
};
Example of Component usage:
<date-rage-component label="Pretty Date" name1="Start" name2="end"/>
Is there a way to automatically stop watching the unused optional bindings, considering they are undefined during compilation?
For instance, if I want to use a component in an application where none of the optional bindings are required, Angular would still create excessive watchers to update ng-if statements that will always evaluate to false.
Am I prematurely optimizing for performance unnecessarily or misunderstanding a concept?
I have considered creating a custom wrapper directive to leverage lazy transclude compilation in angular 1.5
Here is some pseudo-code I came up with:
<optional-binding-once ng-if="::attrs.model1">
<div ng-if="attrs.model1">
stuff
</div>
</optional-binding-once>
This approach aims to compile the code inside optional-binding-once only when ng-if evaluates to true, thereby reducing the number of watchers if a binding is not defined.
(EDIT) Some Insights after conducting tests and research
It appears that there is no straightforward solution to minimize the watcher count within a component when optional bindings are left unfilled.
I ran some tests during the $digest phase of Angular to determine if the increased number of such watchers poses a real issue.
Below are my findings:
The tests were carried out under a worst-case scenario involving 888 components with 4 optional bindings each.
Chrome - Without optional bindings ( 888 component, total watchers 889)
- Total Watchers: 889
- Last Digest Cycle time: 0.9950000000026193
- Average time for the last 1004 digest cycles: 1.0544920318724353 ms
- Starting DOM loading (400ms)
Chrome - With optional bindings ( 888 component, 4 optional bindings, total watchers 4441)
- Total Watchers:4441
- Last Digest Cycle time: 1.1549999999988358
- Average time for the last 1001 digest cycles: 1.6851748251747816 ms
- Starting DOM loading (600ms)
Safari - Without optional bindings ( 888 component, total watchers 889)
- Total Watchers: 889
- Last Digest Cycle time: 1.0849999999991269
- Average time for the last 530 digest cycles: 1.211632075471664 ms
Safari - With optional bindings ( 888 component, 4 optional bindings, total watchers 4441)
- Total Watchers: 4441
- Last Digest Cycle time: 1.7450000000026193
- Average time for the last 588 digest cycles: 2.1167176870748237 ms
Conclusions:
In a worst-case scenario, an increase of 1ms in the $digest time is observed. This elevation does not seem to pose a significant bottleneck in application performance. These types of watchers fail at the first condition of $digest (value = get(current)) !== (last = watch.last) && etc...), thus having minimal impact on processing time as they remain unchanged and do not affect the Angular context!