Change detection is the process of mapping the application state into user interface. In case of the web applications this usually means mapping JavaScript data, such as objects, arrays and other primitives, into the DOM (Document Object Model) which is viewable and interactable by the end-user. Even though the mapping of the state to DOM is somewhat simple and straightforward, great challenges are faced when trying to reflect the changes that have happened on the state to the DOM. This phase is called re-rendering and it usually needs to be performed each time there is change in the underlying data model.
In these two blog posts I will go through all the aspects of Angular 2 change detection starting from the very basics:
- Change Detection Overview (this post)
- Change Detection in Angular 2
If you are already familiar with the concept of change detection and its previous implementations, feel free to skip this introduction post and go straight to part 2.
Change Detection
Change detection is an important issue to solve when building a library or a framework containing view rendering functionality. Let’s first take a look at what is the goal of the change detection and how it has been implemented earlier by other vendors.
Goal
As stated already before, the goal of the change detection is to render the data model to the DOM. This also includes re-rendering when changes occur.
The ultimate goal is to make this process as performant as possible. This goal is greatly affected by the number of expensive DOM accesses needed to fulfill this purpose. But even the minimal possible DOM access doesn’t help if the identification of changed parts is slow. These two aspects are the core to understanding the different approaches taken by different libraries and frameworks as will soon be seen.
The process of change detection centers around two main aspects:
- Detecting that changes (may) have happened
- Reacting to those potential changes
There are multiple approaches to both of the problems and when going through the different solutions to change detection in the next section, these two will be emphasized for each solution. The point of having the word may in parenthesis is that some approaches know exactly if the change has happened or not, while others take a different path and check for changes even if they only may have happened. Even though the latter sounds extremely inefficient and insane, it actually provides great flexibility for application developer and can be implemented efficiently enough.
Evolution of Change Detection Mechanisms
The already somewhat traditional approaches to solve the change detection can be divided into five subcategories based on approach they take as done by Tero Parviainen in his commendable article with title Change and Its Detection in JavaScript Frameworks:
- Server-side rendering
- Manual re-rendering
- Data binding
- Dirty Checking
- Virtual DOM
All of these approaches deserve to be introduced as they lay down the foundation of change detection to build upon.
Server-side Rendering
Before the era of SPAs the state was exclusively stored on the backend and all the state transitions happened via navigating through links or submitting forms. Either way, they required a full page load which is obviously slow and doesn’t offer much of a user experience.
How possible change is noticed:
No changes can happen in the client.
What happens when change may have happened:
Nothing is updated in client. New HTML is rendered each time on server.
Manual Re-rendering
With the rise of JavaScript usage there came also the idea of bringing the data models to the browser, instead of just keeping them on server-side. This idea was popularized by frameworks such as Backbone.js, Ext JS and Dojo, which were all based on the idea of having events fired on state changes. These events then could be caught by the application developer on the UI code and propagated to the actual DOM. Thus the updating of the DOM was still responsibility of the application developer.
How possible change is noticed:
Frameworks define their own mechanisms to be used for data storing so that changes can be tracked. These can be for example objects that are inherited.
What happens when change may have happened:
Event is triggered and can be handled by application developer.
Data Binding
First approaches that can actually be called data binding were based on the observation, that the events could also trigger automatic update to DOM. The main difference compared to the earlier implementations lays exactly on that there is also support for reacting to events caused by changes. One well-known example of these approaches is Ember.js.
Even though the UI updating was now ”automated”, it still had the problem of inconvenient syntax of declaring changes caused by the lack of support by JavaScript. Example of this syntax compared to the what it could be with for example ES6 Proxies is below.
foo.set('x', 42); // The syntax required by Ember.js
foo.x = 42; //What the syntax should and nowadays could be
This kind of awkward syntax made it possible for solutions to detect the changes automatically with minimal effort. It requires though some common API to be used and binds the data model to the framework unnecessarily.
How possible change is noticed:
Frameworks have their own functions like set(key, value) which trigger the change detection automatically.
What happens when change may have happened:
The changed parts are known as they are always set with a setter function. This makes it possible to only update the changed parts to the UI without comparing what may have changed since last rendering.
Dirty Checking
Dirty checking is how Angular.js implements change detection. Every time a change happens set of watches generated for bindings attached to template is ran. These watches will then perform check on whether the data has changed since the last time and if so perform update on DOM.
The name dirty checking comes from the process of checking all the bindings every time there is a possibility of change in the state and an operation called digest is launched. This digestion with iterating and comparing through all the bound values may sound like a lot performance-wise but is actually surprisingly fast as it also minimizes unnecessary DOM accesses.
How possible change is noticed:
Custom implementations for possible change sources like setTimeout ($timeout in Angular.js). If change happens outside of these primitives, the framework needs to be notified. In Angular.js this is done with $scope.apply() and $scope.digest().
What happens when change may have happened:
All the bound values have watcher which keeps the last value in storage and compares it with the current value.
Virtual DOM
Virtual DOM is an approach made famous by React. In it the rendering is done into virtual DOM tree, that is still just a vanilla JavaScript data model. This virtual tree presentation can then be converted into corresponding DOM tree. This is what is done initially, but how about when the changes occur?
When a change is detected a new virtual DOM is generated from a scratch. This newly-created structure is then differentiated against the current presentation of virtual DOM. Patch generated by the diff is then applied to the actual DOM, thus minimizing the need to touch the actual DOM.
How possible change is noticed:
In case of React, the this.setState() needs to be called to set the new state which is then rendered.
What happens when change may have happened:
New virtual DOM structure is composed which is then differentiated with the previous structure and the patch generated from differences is applied to the DOM.
Conclusions
As is seen here, there has been many different solutions to change detection with each of them having its own pros and cons. In the next post we will take a look at how Angular 2 implements the change detection.