Angular 4 Change Detection

Change detection is responsible for updating views when underlying data models change. Whenever a change event occurs, the application must check for changes to the app's state and update the view accordingly. This is what keeps Angular views dynamically in sync with components, directives, etc.

While Angular 1 uses the digest cycle to recursively check the DOM for changes, Angular 2 implements a simpler, faster, and more efficient change detection strategy. In this article, we discuss Angular 2 change events and how change detection works in Angular 2.

Angular 2 Change Events

Change events include:

  • browser events (click, mouseover, keyup)
  • asynchronous activity (setTimeOut(), setInterval())
  • AJAX requests like $http

Whenever a change event occurs, Angular uses the zone.js library to patch existing browser API functions to perform additional tasks for change detection. For example, Angular 2 overrides the default addEventListener() to include additional Angular functionality for responding to the event " the Angular way".

How Change Detection Works in Angular 2

Each component has it's own change detector which tracks changes to properties referenced in templates. For example, if you reference a {{name}} property in your view, the component's change detector will compare the name property's new value with it's old value. If the values are different, the change detector updates the DOM as necessary.

How is this different than Angular 1?

Angular 1 uses two-way data bindings to keep views in sync with data models. This creates a bi-directional MVVM relationship between views and models. If a user input is updated in the view, it will trigger the digest cycle to recursively check the DOM for property changes. Likewise, if the underlying data changes from an asynchronous event (like an $http request), then the same recursive check fires.

While this keeps views in sync with data models, it can quickly cause performance issues if enough watchers are added to the DOM. Since the entire DOM is recursively checked for each event, the digest cycle can cause unpredictable cascading changes that slow things down exponentially.

Unidirectional data flow

Unlike Angular 1's two-way data bindigns, Angular 2 uses unidirectional data flow to detect changes. Similar to React's flux architecture, data flows in one direction every time a change event occurs.

Angular 2 leverages the component tree to ensure that data always flows from top to bottom. Every component you create in Angular 2 has its own change detector. When a user event occurs, change detection occurs from top to bottom in the component tree, starting with the root component.

This top down strategy is more predictable than the digest cycle. It ensures everything gets updated in one run. Additionally, we know that every change we see in our view came from the underlying component (and not from changes made to the view itself).

Why is this better?

Angular 2 change detection works better with modern just-in-time (JIT) compilers. Since every component essentially has it's own blueprint, the browser avoids having to dynamically compare properties at runtime. This drastically improves performance as the compiler can more accurately predict app behavior.

This method of change detection also allows Angular to only update the parts of the DOM that have actually changed. While Angular 1 updates the entire DOM with each check, Angular 2 only redraws the elements that need updating. Since DOM manipulation is one of the most expensive JavaScript operations, this drastically improves speed and efficiency.

Angular 2 Manual Change Detection

You can manually control change detection for a component by explicitly setting the changeDetection property in the @Component decorator. For example, if you set changeDetection: ChangeDetectionStrategy.OnPush then your component will only fire change detection when its inputs change (and not for every event).

Immutability and OnPush

The main advantage of OnPush is gives you more control over change detection. By specifying when a component's change detector fires, you can avoid running unnecessary checks when inputs for that component haven't change.

The one caveat with this is that OnPush only works with immutable objects. You must create a new reference to that property for the component to recognize the change. Since JavaScript objects are inherently mutable, you have to make sure you are creating a new reference to the property or change detection won't fire. For example:

person.name = "Sam"
just changes the name property of an existing reference. With OnPush change detection, your component will not register this as a change. Instead, you would do something like:

person = new Person({
    name:"Sam"
})

This creates a new reference to a Person and OnPush change detection will recognize this as a change.

While things can get messy fast, you can use libraries like immutable.js to force immutability. It should also be noted that the default change detection mechanism in Angular 2 is compatible with direct object mutation. You don't have to worry about immutability unless you are using OnPush.

Conclusion

Chances are you won't need to use OnPush to manually control change detection. The default mechanism works fine for most apps and is still a huge improvement on Angular 1. If your app is particularly sensitive to performance or you are dealing with larger data sets, manual change detection may be worth looking into. Otherwise you can safely rely on Angular 2 change detection to handle events and update the DOM as necessary.

Your thoughts?