How Angular detects changes

angular change detection internals

Hey Guys!

Every developer wants to build fast and robust Angular applications. But to be able to, you need to dive into the Angular internal structure and obtain comprehensive understanding how it works and sometimes it is not that easy from the first try.

In this article, I'll try to give you a superficial idea of how Angular works under the hood not going in too much of detail.

The main concept of the Angular internals that we need to discuss is Change Detection - process that checks if data has been changed and re-renders components’ templates.

Let's break it into the following topics:

Internal representation

When you build an Angular application you start from AppComponent, then you create more and more components, combine them in modules and use these components here and there. Thus, we can imagine Angular application as a tree of components, but it only scratches the surface. Let's figure out what the Angular application actually consists of.

If you create a simple component like this:

@Component({
  template: `
    <span>I am {{name}}</span>
  `,
})
export class MyComponent {  
  name = 'Angular';
}

and build your application in aot mode, you'll be able to find component factory generated by the Angular compiler. It will look like a simplified factory:

// simplified component factory
function View_MyComponent_0(l) {  
  return jit_viewDef1(0,
    // view defenition nodes
    [
      jit_elementDef2(0, null, null, 1, "span", ...),
      jit_textDef3(null, ["I am ", ...])
    ]
  );
}

Component factory describes the structure of the component view and is used when instantiating ViewDefinition.

export interface ViewDefinition {  
  ...
}

ViewDefinition is like a template which is used when instantiating components. For each component, Angular instantiates ViewData that contains component instance, its state, children, parent and bounded properties.

As we can see now, the Angular application is not just a tree of components but a tree of ViewData instances and our components are just fields of that view data instances. Moreover, Angular runs change detection process over ViewData, not over components itself.

interface ViewData {  
  viewContainerParent: ViewData|null;
  component: any;
  // child nodes
  nodes: {[key: number]: NodeData};
  state: ViewState;
  oldValues: any[];
  ...
}

Who triggers change detection?

Ok, now we know how angular represents our applications and what instances are used to perform change detection’s tasks.

Before digging into change detection process I want to tell you what actions and events are responsible for triggering this process:

More details you can find by the links above but let's focus on Zone.js now.

Zone.js

I don't want to be too specific about Zone but in few words, you can think about it as a tool that provides you with a capability to hook into your code execution process and track when a part of your code has started or finished execution. For example in the case below we use afterTask hook that will be run when a code in the run method finished its execution, including asynchronous tasks. So this hook will be executed only when setTimeout is completed:

Zone.current  
  .fork({
    afterTask() {
      // do something after leaving zone
    }
  })
  .run(() => {
    // do something async
    setTimeout(() => ...) 
  });

And that’s a point. Zone is an execution context that persists across async tasks. Angular has Zone wrapper called NgZone that gives a capability to do something similar to the code below, meaning, subscribe to events fired by zone and run change detection loop:

ZoneEventsStream.subscribe(() => {  
  this.changeDetectorRefs.detectChanges();
});

Can I run something outside change detection?

Angular subscribes to the most of the browser events and runs change detection as a reaction but if you want to run some process that doesn't require DOM updates you can run it outside change detection within runOutsideAngular method of the NgZone. This may be useful for sending analytics to the server from time to time because we don't need to update application UI during this process.

export class AppComponent {  
  constructor(private zone: NgZone) {
    this.zone.runOutsideAngular(() => {
      // run code outside angular change detection

      setInterval(() => this.sendStatistics(), 1000);
    });
  }
}

Change detection flow

It's time to investigate what change detection exactly does. If it’s been triggered it starts from the root component, checks it and proceeds to the children. As you can see in the picture below change detection can be triggered on any component and always starts from the application root.

During the change detection on a component angular does the following steps:

Let's imagine that we have three components:

Change Detection flow for these components will be the following:

OnPush Strategy

As we can see, calling ngOnChanges and ngDoCheck hooks doesn't mean component has been checked. These hooks are called when change detection process is focused on the component parent, so ngDoCheck of the B component will be called when change detection is checking A component.
This may lead your application to the strange behavior when you're using OnPush strategy.

I mean, if we make changeDetectionStrategy of B component OnPush and don't change input props B component will not be checked but ngDoCheck hook will be triggered.

OnPush Strategy Emulation

For a better understanding how OnPush strategy works, let's see how we can emulate it. Using ChangeDetectorRef of the component we can detach it when instantiating and stop detecting changes on it. Anyway, as we know from examples above ngOnChanges hook will be called on this component even though it's detached because it'll be called when change detection checks its parent.

ngOnChanges will be called only if property binding has been changed. Sounds similar to the OnPush strategy, right? So, if input bindings have changed we have to reattach component and allow change detection process it. And then detach it again asynchronously.

export class ChildComponent implements OnChanges {  
  constructor(public cd: ChangeDetectorRef) {
    this.cd.detach();
  }
  ngOnChanges(values) {
    this.cd.reattach();
    setTimeout(() => {
      this.cd.detach();
    })
  }
}

DOM updates

I think it's time to discuss the most important step of change detection - updating DOM representation of the checked component.

Do you remember this simple component from the first part of the article?

@Component(
  template: `
    <span>I am {{name}}</span>
  `,
)
export class MyComponent {  
  name = 'Angular';
}

Let's look closer to another part of the component factory that wasn't mentioned previously:

// component factory
function View_AComponent_0(l) {  
  return jit_viewDef1(
    ...
    // update renderer
    function (_ck, _v) {
      var _co = _v.component;
      var currVal_0 = _co.name;
      _ck(_v, 1, 0, currVal_0);
    }
  );
}

The purpose of this function is to check and update component DOM representation if bindings are changed.

function (checkAndUpdate, viewData) {  
  var component = viewData.component;
  var currentValue = component.name;
  checkAndUpdate(viewData, 1, 0, currentValue);
});

Update renderer function receives checkAndUpdate function and ViewData instance that contains current component. It calls checkAndUpdate for each binding in the component template. If you have multiple bindings in template like these:

<h1>Hello {{name}}</h1>  
<h1>Hello {{age}}</h1>  

Update renderer will perform multiple calls of the checkAndUpdate:

function (checkAndUpdate, viewData) {  
  var component = viewData.component;
  // here node index is 1 and property is `name`
  var currentValue_0 = component.name;
  checkAndUpdate(viewData, 1, 0, currentValue_0);

  // here node index is 4 and bound property is `age`
  var currentValue_1 = component.age;
  checkAndUpdate(viewData, 4, 0, currentValue_1);
});

checkAndUpdate function is just a router that chooses specific checkAndUpdate* function depends on binding type:

function checkAndUpdate(view: ViewData, nodeIndex: number, ...): boolean {  
  switch (view.def.nodes[nodeIndex].flags & NodeFlags.Types) {
    case NodeFlags.TypeElement:
      return checkAndUpdateElement(view, nodeDef, ...);
    case NodeFlags.TypeText:
      return checkAndUpdateText(view, nodeDef, ...);
    case NodeFlags.TypeDirective:
      return checkAndUpdateDirective(view, nodeDef, ...);
  }
}

I think the simplest checkAndUpdate function is checkAndUpdateText and its code looks like the snippet below. It just checks that rendered values and new ones that aren't the same and then updates component DOM:

function checkAndUpdateText(view: ViewData, nodeIndex: number, ...): boolean {  
  if (checkAndUpdateBinding(view, nodeDef, ...)) {
    // update DOM here
  }
}

This means that each property of component template will be checked and updated separately, and if you changed only one text node of the component only this text node will be re-rendered, not all the template. This behavior helps angular reach high-speed rendering.

Why we need ExpressionChangedAfterItHasBeenCheckedError?

Angular has another important underlying concept that helps us to keep view and model of an application consistent. Let's look at this example:

@Component({
  selector: 'parent',
  template: `
    <span>{{ name }}</span>
    <child></child>
  `
})
export class ParentComponent {  
  ...
}
export class ChildComponent implements AfterViewInit {  
  @Input() text;
  constructor(private parent: ParentComponent) {}
  ngAfterViewInit() {
    this.parent.name = 'updated name';
  }
}

According to the change detection steps for this example:

ngAfterViewInit of the ChildComponent will be called after updating the DOM of the ParentComponent. However, ngAfterViewInit changes parents name property which rendered in the parent template. Do you see that? Something goes wrong... Change detection has rerendered ParentComponent and then its child changes its name property.

And after that, we have inconsistency - rendered name property of the ParentComponent isn't equal actual name property because it has been changed after rendering.

Great, we’ve defined the problem, so has Angular. This is the reason why angular runs second change detection loop in the development mode for verification purpose. This verification loop checks that actual properties in components equal rendered and bounded properties and if they do not, then angular throws ExpressionChangedAfterItHasBeenCheckedError.

Let's check another example which reveals property binding issue:

@Component({
  selector: 'parent',
  template: `
    <span>{{ name }}</span>
    <child [text]="text"></child>
  `
})
export class ParentComponent {  
  ...
}
export class ChildComponent implements OnInit {  
  @Input() text;
  constructor(private parent: ParentComponent) {}
  ngOnInit() {
    this.parent.name = 'updated name';
  }
}

We have a quite similar example to the previous one but with two differences:

1) ParentComponent passes input property for its ChildComponent
2) ChildComponent changes ParentComponent.text property in its ngOnInit hook

This code leads us to the similar error as mentioned above, but why?

It changes input bindings between parent and itself. As you remember from Change Detection Flow part, the order of change detection operations is the following for this example:

But in the snippet, we change parent input binding in ngOnInit hook - after binding has been updated. Verification loop checks this inconsistency and throws the exception.

If you have this error it means you're doing something wrong with updating component properties when it had already been rendered or bounded.

To avoid such error, you have to remember about change detection operations order and update your data in the right lifecycle hooks.


Hopefully, you've found this topic useful. Keep reading our blog and see you on our meet-ups!