4
votes

I've read that Angular 2 change detection is uni-directional, heading from the top to the bottom of the component tree, and that it gets stable after a single pass, meaning that there are no multiple cycles of change detection. Given those assumptions, what will happen in a situation where we have a parent and a child component which have properties that are interdependent? Example:

  1. Based on an user event, parent component updates a child component property
  2. This update fires an event in the child component that updates a property on the parent
  3. Parent property update fires another event that updates the child component
  4. ...

From my understanding, similar situation in Angular 1 was resolved by setting a limit on the number of cycles triggered by these interdependent properties, which would cause the framework to throw an error.

How is it resolved with Angular 2? At which point in the above example is the change detection actually triggered?

3

3 Answers

8
votes

I've read that Angular 2 change detection... gets stable after a single pass

Angular 2 doesn't "get stable". With Angular 2 apps, we are responsible for writing our app in such a way that it is always stable after a single pass.

By default (e.g., you are not using the OnPush change detection strategy on any components, nor did you detach() any components), change detection works as follows:

  • A Zone.js monkey-patched asynchronous event fires – e.g., a (click) event, an XHR response, a setTimeout() timer. The callback associated with that event runs, which can change any view or application data in our app. Then, because of the monkey-patch, Angular change detection runs. In other words, by default (e.g., you are not manually triggering change detection), only a monkey-patched asynchronous event triggers change detection.
  • Starting from the root component, and traversing down through the component tree (depth-first traversal), each data binding is checked for change. If a change is found the change is "propagated". Depending on the template binding type, propagation may
    • propagate the changed value to the DOM. E.g., when {{}} binding is used, the new value is propagated to the textContent property of the appropriate DOM element.
    • propagate the changed value to a child component. E.g., when using input property binding ([childInputProperty]="parentProperty"), the new value is propagated to the child input property.
  • If you are in dev mode, all of the components are dirty checked again, but no propagation occurs. This second dirty check helps us find problems with our code, e.g., if we violated the idempotent rule, which is fancy way of saying that one of our bindings (its template expression) has side effects. In other words, the extra dev mode checks let us know if if our code is not stable after a single pass.

Side effects are not allowed in Angular 2. In reference to your question, a child component therefore must not modify a parent property as a result of input property propagation. So, you could say that Angular 2 "resolves" the situation you asked about by not allowing it.

This is not as bad as it might sound. The only way I'm aware of that an input property propagation can change a parent property is if the child component implements a setter method for the input property which modifies another property that the parent displays in its template. (Here's an old plunker that does this -- see the @Input set backdoor() method.) Normally you wouldn't do this. If you do need to do this, then Günter's comment is spot on: do the change inside a setTimeout(), hence it will be part of the next change detection cycle.

I want to reemphasize: event handlers run before change detection, so they are free to change any data in our app -- local/component view data, application data, whatever. So in an event handler, a child component is free to change parent data. E.g., suppose both parent and child have a reference to the same array. When an event handler runs, the parent and/or child component can modify that array.

So if you make changes in event handlers, no problem. There is only a problem if your setter does something odd.

3
votes

In devMode change detection does twice successive turns. If the 2nd turn recognizes changes it throws. Therefore change detection itself must not cause a change of the model.

Angular2 change detection only updates from parent to child. Because change detection itself must not cause changes, change detection is completed when all changes are propagated onto the leaf nodes.

When an event or any other async call happens, it is fully processed and can also cause changes from child to parents (outputs). When this is completed, change detection once propagates from root to leaves.

This way Angular2 change detection avoids cycles.

0
votes

It's true that the change detection is executed in a single pass, but there can be multiple cycles following a single event.

The article Does Angular applications become stable after a single change detection cycle? explains this point with illustrative examples.