The Angular 2 change detection system is somewhat of a black box: you update some variables in the model, and the components update automatically. Thoughtram, Victor Savkin, and other websites have written some excellent posts explaining change detection (which we reference in this post). Often, these posts present a series of graphs and diagrams that illustrate how the detect-and-update machinery works theoretically. However, in the spirit of “programming by poking,” we will attempt to match some of the theories with observable behavior using an Angular 2 demo app, and the code for the demo can be found in this plunker or this GitHub repo.
About the Demo
Surface Level Functionality
The above app renders a binary expression tree for an algebraic expression of the form:
Where the operator:Could either be:Or:
Or:The numbers are restricted to non-negative integers less than 10.
If you play around with the expression tree app for a bit and change the operators and numbers, you will see the nodes flash. Each of these nodes is rendered by its own Angular 2 component, and these flashes correspond to certain change-detection-related events (we will discuss these events in more detail in the next section). Of course, the events triggering these flashes occur in such rapid succession that the nodes seem to all light up at once. For this reason, the demo provides controls to record and replay the flashes one step at a time, as they occurred in real time.
The following diagram (Figure 1) summarizes the basic functionality we have described so far:
Figure 1. Explanation of the playback features and an explanation of the node components.
Exposing Angular 2’s Change Detection System
To reiterate what was said in the introduction, our aim is to match descriptions of Angular 2’s change detection system to observable behavior. The expression tree app’s implementation lends itself to this purpose in a couple of ways.
First, the flashes provide a visual log of the change detection system at work. Angular 2’s component lifecycle hooks make this possible. These hooks get called at key points during the change detection process, giving us a programmatic window into the change detection process as it unfolds. For example, the node components trigger a green flash when
ngOnChanges is called, a blue flash when
ngDoCheck is called, and a purple flash when
ngAfterViewChecked is called. An additional yellow flash is emitted when the component’s internal representation of the expression (i.e., the expression model) gets updated.
Second, although it appears the app is rendering an expression tree, it will be much more helpful to set the numbers, operators, and expressions aside and look at the tree as a literal component tree (if you are unfamiliar with the idea of a component tree, it might be worth reading the aside below). If you remember from the previous section, we mentioned that each node is rendered by its own component. The component rendering the topmost node is, in fact, the parent of the components rendering the topmost node’s child nodes; the components for the child nodes are the parents of the components rendering their child nodes; and so on. Change detection is often described as traversing the component tree.
A Few Experiments
When Change Detection Is Performed
Let us try to verify some basic change detection behavior. First, we will experiment with the triggers that initiate change detection. As Pascal Precht explains in his article, Angular 2 assumes that any model changes are the result of asynchronous events. This is certainly the case for our expression tree app; the expression model is only updated when the user clicks and changes the values of the drop-down menus. Thus, after any asynchronous event, change detection is triggered.1
To start off, let us click the record button. Notice that as soon as we do so, the nodes flash and the log is filled with messages. Change detection was just triggered. This behavior verifies Precht’s explanation: the record button itself is a component, it has a mousedown handler attached it, and our click triggered an asynchronous event, which caused Angular 2 to check for changes.
Admittedly, it is undesirable that our demo app works like this. Ideally, we would just record the change detection cycles spawned by working with the expression tree components, but we have opted to just live with this side effect of clicking record. As we move forward with the next experiment (and as you do your own), just remember where this initial change detection cycle—with its initial messages and flashes—comes from.
Component Tree Traversal
Precht’s same article states, “Change detection is also always performed from top to bottom for every single component, every single time, starting from the root component.” Let us try to verify this idea as well. With the demo app in its initial state, hit record, change the leftmost number from three to five, and then hit record again to end the recording. Now hit the play button. We will skip all the flashes that were triggered by hitting the record button by scrolling down and clicking on the first log message that says, “Expression update: 3 -> 5” (see Figure 3).
Figure 3. The log message for the first model-change event.
Use the right playback arrow to step forward through the flashes. Notice that after the model changes settle (i.e., after all of the yellow messages), the change detection system follows a depth-first search pattern. The root node first flashes green as Angular 2 detects changes to the root’s rendered model and calls
ngOnChanges. It then flashes blue as Angular 2 calls
ngDoCheck and allows the root node to detect additional changes. The flashes then travel along the leftmost child nodes before going to the right. As the downward traversal unwinds, purple flashes are emitted, indicating
ngAfterViewChecked has been called and change detection has concluded for the component subtree rooted at that node. Again, the description nicely matches what we see.
What other experiments might we run? For one, we might consider how the entire component tree lights up on each change detection cycle. This behavior indicates that—out of the box—the change detection system performs an expensive and exhaustive search across the components. Luckily, we can pare this search down by marking our components as “on push” or by tweaking our inputs to be observables. We could try implementing one of these and seeing how the flash pattern changes. We could also experiment with detaching a component from change detection. There are many opportunities to “poke” at Angular 2’s change detection system. So roll up your sleeves, make a fork of the expression tree app, or perhaps take a look at the
PeekABooComponent in the official documentation, and start experimenting.