Framework: How does Aurelia's DOM UI updating strategy work?

Created on 6 Apr 2016  路  14Comments  路  Source: aurelia/framework

How does Aurelia templating compare to other libraries like React, Angular, Meteor Blaze? What are the differences and similarities not just in outter usage, but in implementation details and techniques (f.e. React uses a dom-diffing strategy to update the DOM).

Has this been written about somewhere? Searching for "aurelia vs anything-else" on Google doesn't really help, as everything that I find compares just outter usage.

question

Most helpful comment

There's an entire "Read the Source" webcast available where @jdanyow explains the binding engine and how it works. DOM-diffing isn't the only way to achieve performance. In fact, Aurelia consistently beat React by a large margin in all 3rd party benchmarks by taking a different approach entirely. The basic idea is to make binding asynchronous, batch changes and dom updates, and eliminate any dirty checking (whether Angular-style model dirty checking or React style virtual DOM dirty checking.) This is much more efficient. We know exactly what changed, when it changes and we can batch that together with as a single DOM update with any other changes.

We pride ourselves on being able to integrate with other technologies, so if you also like React you can easily use that to render individual components or use any React component in your Aurelia app. @Vheissu wrote that article when he was using some of these techniques last year.

All 14 comments

Rob did a webinar that touches on the implementation techniques. He mentions that aurelia uses an observable mechanism instead of dirty checking which removes the need for dom-diffing at about 1:02:55 and 1:05:44.

How does it work exactly? Does the (deeply?) observed object have the same structure as the DOM? If so, that might make sense (as an alternative to dom diffing).

Any thoughts on which performs better (theoretically)? I guess this leads to the question: which is heavier, the dom diffing algo, or the observable implementation?

Why was this closed without an answer? I am not sure why @EisenbergEffect says DOM-diffing is not needed, can someone show an example as to how Aurelia solves complex nested component DOM updates? There is also this blog post, so why are people blogging about this if it is not really needed? What are we failing to see? A side by side comparison would be nice.

There's an entire "Read the Source" webcast available where @jdanyow explains the binding engine and how it works. DOM-diffing isn't the only way to achieve performance. In fact, Aurelia consistently beat React by a large margin in all 3rd party benchmarks by taking a different approach entirely. The basic idea is to make binding asynchronous, batch changes and dom updates, and eliminate any dirty checking (whether Angular-style model dirty checking or React style virtual DOM dirty checking.) This is much more efficient. We know exactly what changed, when it changes and we can batch that together with as a single DOM update with any other changes.

We pride ourselves on being able to integrate with other technologies, so if you also like React you can easily use that to render individual components or use any React component in your Aurelia app. @Vheissu wrote that article when he was using some of these techniques last year.

@EisenbergEffect Thanks for the response. I think I misread the initial question, I did not realize that it was about performance. Sorry about that.

My question is not related to performance, but rather how to do React-like development, where a small change in a large and complex data structure can be pushed to the DOM. I am trying to find examples of how to achieve this, but not finding so far. There is no question in my mind that your approach should be faster, the success of React was that DOM-diffing is actually more than fast enough (which was unexpected at the time) while providing great developer efficiency and state predictability.

I want to see an example with nested components getting updated partially in async fashion. I have not found such an example yet. I watched the video at https://www.youtube.com/watch?v=NyxGZYgOCuo but it does not answer this question. Where can I find the best practice for this approach?

@serkanserttop There's just not anything to show. Aurelia only updates what changes. It handles nested components automatically. It just works. I'm not sure what you are asking to see actually.

Thanks @EisenbergEffect. We have a bug in our code that refreshes the UI instead of partially updating it, but I guess it is related to something else. I might have led to the wrong conclusion about the root cause. I will ask on gitter if I need help, no need to clutter up here.

@serkanserttop if you can put together a minimal repro on gist.run I'll try to help figure out the issue. (or putting together the repro might end up helping you figure out the issue)

@EisenbergEffect

Aurelia only updates what changes.

(And the video that @Rfvgyhn linked to says the same thing, but doesn't explain how it works except that it uses an Object.observe-like API)

I know I can just read the source code and find out, but that's not what I want to do right now as I don't have time for that at the moment.

Is there a high-level document describing Aurelia's approach? What would the architecture diagram look like (is there a diagram somewhere)? If there's no doc or diagrams yet, it would be great to see those.

React describes exactly how their dom-diffing strategy works. But, Aurelia doesn't describe how the Aurelia strategy works as transparently as React describes theirs.

The Aurelia docs describe the outer API, and expect us to take for granted that "it just works", but that's not enough for some of us.

It would be greatly appreciated Rob. :]

I updated the title to better reflect the topic.

Here is an hour long webcast with our databinding engineer, walking through the source code and explaining various aspects of how it works: http://hangouts.readthesource.io/hangouts/aureliaio-data-binding/

Thanks @EisenbergEffect! I believe that clears it all up. Interesting stuff. (I missed the links to readthedocs)

The strategy isn't that complicated, and reading the source wouldn't take that long. Basically, Aurelia wraps observed properties so that when they are changed, anything that has subscribed to be notified of changes will be notified. This is done on the micro task queue to improve performance and to get rid of thrashing.

When it comes to observing changes in dom elements, Aurelia subscribes to the certain events that are fired when a property would be changed. It then updates the property on the object that dom element was bound to.

It is a very "reactive" system. Aurelia reacts to DOM events and to observed properties changing. It's also a system that has proven, in real world examples, to be much more performant than DOM diffing.

@jdanyow is working on a longer explanation that will be in our documentation shortly.

@AshleyGrant Thanks a lot for the offer to help but the code base is large, private and a bit difficult to narrow it down.
As @jdanyow will rewrite the docs, one suggestion I have is if he can touch on which data structures work with this approach.

<div repeat.for="item of items">
    <p>${item.name}</p>
    <p>${item.count}</p>
</div>

For example I have been trying to understand if reassigning works or not:
this.items.push(...)
vs
this.items = newItems
It looks like it does, but I am not 100% certain why it works with the second case, what is the magic behind it? When Aurelia wraps the observed properties for an array, does it also take into account the index, or how else does it figure out?

Another thing I wanted to test was when items are sorted:

function getRandomIntInclusive(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
//append and increment
activate() {
    let i = 0;
    setInterval(() => {
        this.items = this.items.slice().concat({name:i++, count:-1}).map((item) => {
            item.count++;
            return item;
        });
    }, 1000);
}
//append and sort randomly
activate() {
    let i = 0;
    setInterval(() => {
        this.items = this.items.slice().concat({name:i++, count: 0}).sort(() => {
            return getRandomIntInclusive(-1, 1);
        });
    }, 1000);
}

When items are appended only, I can see that the <p>${item.count}</p> sections are only updated (from the flashing from the Developer Tools) and the rest stay put.
In the sorted case, everything flashes obviously, but I am not sure if there are any caveats we need to think about with these approaches (regarding events, nested components within, etc).

Also one question that might help me and others to better understand is, if there is an array of lots of items and its internal values don't change, but it is continuously sorted. In this case, does Aurelia rebuild everything all the time or shuffle the items around? If it shuffles around, how does it know to do that without having been given a specific id key?

Was this page helpful?
0 / 5 - 0 ratings