Incremental DOM is used internally at Google, and it is defined by this key idea:
Every component gets compiled into a series of instructions. These instructions create DOM trees and update them in-place when the data changes.
For instance, the following component:
@Component({
selector: 'todos-cmp',
template: `
<div *ngFor="let t of todos|async">
{{t.description}}
</div>
`
})
class TodosComponent {
todos: Observable<Todo[]> = this.store.pipe(select('todos'));
constructor(private store: Store<AppState>) {}
}
Will be compiled into:
var TodosComponent = /** @class */ (function () {
function TodosComponent(store) {
this.store = store;
this.todos = this.store.pipe(select('todos'));
}
TodosComponent.ngComponentDef = defineComponent({
type: TodosComponent,
selectors: [["todos-cmp"]],
factory: function TodosComponent_Factory(t) {
return new (t || TodosComponent)(directiveInject(Store));
},
consts: 2,
vars: 3,
template: function TodosComponent_Template(rf, ctx) {
if (rf & 1) { // create dom
pipe(1, "async");
template(0, TodosComponent_div_Template_0, 2, 1, null, _c0);
} if (rf & 2) { // update dom
elementProperty(0, "ngForOf", bind(pipeBind1(1, 1, ctx.todos)));
}
},
encapsulation: 2
});
return TodosComponent;
}());
The template function contains the instructions rendering and updating the DOM. Note that the instructions aren’t interpreted by the framework’s rendering engine. They are the rendering engine.
Why did the Google team go with incremental DOM instead of virtual DOM?
They have one goal in mind: applications have to perform well on mobile devices. This mainly meant optimizing two things: the bundle size and the memory footprint.
To achieve the two goals:
When using incremental DOM, the framework does not interpret the component. Instead, the component references instructions. If it doesn’t reference a particular instruction, it will never be used. And since we know this at compile time, we can omit the unused instruction from the bundle.