Not all observeProperty goes into RAF.
How connect-queue works
The first 100 connect calls will execute immediately. Only when the number of bindings that need to be connected is higher than 100 will the items past the first 100 be queued with RAF.
Then there is also a limitation on how many are executed within a RAF. If the queue has been processing for 15ms, the remainder will go with the next RAF (this algorithm is a bit naive as the real budget of performing work is typically less than 7ms, this is a point for improvement).
Effectively what this does, is it ensures that your page will always load after no more than initialization time + 15ms + time to attach everything. It puts an upper bound on by how much the observation can slow down page loads.
It also means that for a very large number of bindings, your app will continue processing the initialized observations after the page appears to be done loading. This is likely what’s causing the several clogged-up RAF’s you see occurring after loading a view with lots of data in it.
To put it in other words: it’s the “price” you’re paying for the large number of items. The alternative would be to have to look at a completely not loaded page for the time that it currently takes for the RAFs to be done.
Ways to optimize
virtual-repeat
You already know about the virtual repeater. Technically this could be built upon to make it adapt to dynamic item height - this is something I’ll be experimenting with in vNext at least.
You might be able to “hack” around the non-deterministic height problem, as long as you know the minimum height of an element, to simply give it the lowest possible height.
It will render a bunch of off-screen items but still likely far fewer than the normal repeater does now.
when-visible
jdanyow has created a binding behavior for this sort of problem, which causes your bindings to only be updated if they’re visible on screen. I don’t know precisely how it works (still need to look into it myself) but in any case, it might be of use to you: https://github.com/jdanyow/aurelia-when-visible
use signals instead of observation
I’m not a fan of this idea because it kind of goes against the spirit of Aurelia. But if the other solutions aren’t viable and this is a critical thing to your app, you could just make everything one-time
/ & oneTime
and use signals to update everything.
In vNext we’ll be addressing this with a more generalized switch for observation methods, in vCurrent it’s not possible to do this very cleanly unfortunately.
I also noticed you use & oneWay
in your repeaters. This is redundant, just FYI. Collection bindings (or really any bindings that are not targeting form elements) are one-way
/ to-view
(same thing) by default.