Understanding the Expression Changed Error

The ExpressionChangedAfterItHasBeenCheckedError is among the most commonly discussed Angular topics on developer forums. Most questions arise because developers don't fully grasp why this verification check exists in the first place. Some even consider it a design flaw. But for Angular, this error serves as a mechanism to enforce unidirectional data flow and guarantees that the UI reflects the current application state after a single change detection pass.

Unidirectional data flow means that after Angular processes bindings for a component, you cannot modify properties that those bindings depend on. Angular includes a checkNoChanges method that executes post change detection and re-evaluates binding expressions. If during this validation Angular finds that an expression yields a different value than it did during the previous detectChanges pass, it raises the ExpressionChangedAfterItHasBeenCheckedError.

A binding connects a property name to an expression that generates its value. The rendering engine (Ivy) encodes these bindings as instructions that Angular's compiler inserts into the component's template function. When Angular checks a view during change detection, it processes all bindings by running their associated instructions. For each binding, it evaluates the expression and compares the result with the previous value. This is what's known as dirty checking.

When the values don't match, Angular updates the property specified by the binding during the normal change detection cycle initiated by detectChanges. However, in the special checkNoChangesMode activated by checkNoChanges, a detected difference causes the Expression Changed error instead of updating the binding.

The function responsible for detecting this mismatch and raising the error is bindingUpdated. This is a simplified version of its implementation:

export function bindingUpdated(lView, bindingIndex, value) {
  const oldValue = lView[bindingIndex];

  // no update is needed
  if (Object.is(oldValue, value)) {
    return false;
  } else {
    // if we're in development mode and the values are not equal,
    // throw the error and return without updating the binding
    if (ngDevMode && isInCheckNoChangesMode()) {
      const oldValueToCompare = oldValue !== NO_CHANGE ? oldValue : undefined;
      if (!devModeEqual(oldValueToCompare, value)) {
        const details = getExpressionChangedErrorDetails(...);
        throwErrorIfNoChangesMode(oldValue === NO_CHANGE, details.oldValue, ...);
      }
      return false;
    }
    lView[bindingIndex] = value;
    return true;
  }
}

By searching how bindingUpdated is used, we can identify which Ivy instructions might trigger this error:

Image alt

There's also a unit-test that verifies the error production logic. The test checks whether the property instruction, which updates the [id] binding, correctly throws the error:

class MyApp {
  unstableStringExpression: string = 'initial';

  ngAfterViewChecked() {
    this.unstableStringExpression = 'changed';
  }
}

it('should include field name in case of property binding', () => {
  const message = `Previous value for 'id': 'initial'. Current value: 'changed'`;
  expect(() =>
    initWithTemplate('<div [id]="unstableStringExpression"></div>')
  ).toThrowError(new RegExp(message));
});

Test specifications use the ngAfterViewChecked hook for property updates because it fires after bindings are processed. In the mentioned spec, the component property unstableStringExpression starts with the value "initial". Setting it to "changed" inside ngAfterViewChecked causes the checkNoChanges verification to throw the ExpressionChangedAfterItHasBeenCheckedError when it detects the difference.

The root cause of this error is always identical – a binding receives different values during the regular detectChanges run compared to the validation detectNoChanges pass. Therefore, the primary solution remains the same: make sure the expression returns consistent values for both the regular and verification cycles.

This test suite effectively covers all scenarios where bindings can produce the error:

initWithTemplate('<div [id]="unstableStringExpression"></div>');
initWithTemplate('<div id="Expressions: {{ a }}');
initWithTemplate('<div [attr.id]="unstableStringExpression"></div>');
initWithTemplate('<div [style.color]="unstableColorExpression"></div>');
initWithTemplate('<div [class.someClass]="unstableBooleanExpression"></div>');
initWithTemplate('<div i18n>Expression: {{ unstableStringExpression }}</div>');
initWithTemplate(
  '<div i18n-title title="Expression: {{ unstableStringExpression }}"></div>'
);
initWithHostBindings({ '[id]': 'unstableStringExpression' });
initWithHostBindings({ '[style.color]': 'unstableColorExpression' });
initWithHostBindings({ '[class.someClass]': 'unstableBooleanExpression' });

The specific fix depends on what causes the binding to receive conflicting values. To identify the source, we need to determine which binding gets different values and where those differences originate. Finding this root cause often isn't straightforward and requires various debugging approaches to isolate the issue – we'll explore these techniques in detail in the next section on identifying the primary cause.

Let's examine a basic demonstration of how this verification works in practice.

How the Error Detection Works

To observe the error in action, let's create a component that displays a random number generated by Math.random():

@Component({
  selector: 'p-cmp',
  template: `
    <h3>
      <button (click)="noop()">Generate number</button>
    </h3>
    <div [textContent]="number"></div>
  `,
})
export class P {
  noop() {}
  get number() {
    return Math.random();
  }
}

The number expression used in the [textContent] binding will produce different values during normal and verification change detection cycles. As a result, running this application will generate the error:

Image alt

The primary issue is that Math.random() generates different values with each invocation. This means the value assigned to the textContent binding during detectChanges will never match what's produced during checkNoChanges.

The error message provides this information:

ERROR Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
Previous value for 'textContent': '0.9449286101652989'. Current value: '0.1686897170657191'.
Find more at https://angular.io/errors/NG0100

As expected, the error confirms that the textContent binding expression produced different values during the regular change detection and the verification pass. The property instruction processes this binding, which appears in the callstack. The error originates from the bindingUpdated function we discussed earlier:

Image alt

We can also observe this in Chrome Developer Tools:

Image alt

This basic example might seem too simple, especially since the error typically appears in more complex scenarios. Can you identify an obvious fix for this case?

This specific scenario matches one of the specs from the unit test suite mentioned earlier:

initWithTemplate('<div [id]="unstableStringExpression"></div>');

Real-world cases are typically far more complex. They often involve component hierarchies with interactions between parent and child components. Those interactions aren't always direct – they can occur through services, event emitters, or observables. It requires solid understanding and practical experience to quickly identify the underlying cause.

To demonstrate how the error works with a component hierarchy, let's examine a basic parent-child setup.

Modifying Ancestor State

We'll establish a simple hierarchy with two components. The parent component defines the text property used in interpolation binding. The child component injects the parent through dependency injection and modifies its text property within the ngAfterViewChecked hook. As with the test suite, this hook runs after bindings are processed.

The setup looks like this:

@Component({
  selector: 'asr-cmp',
  template: `
    <h3>Ancestor text: {{ text }}</h3>
    <dst-cmp></dst-cmp>
  `,
})
export class Ancestor {
  text = 'initial';
}

@Component({
  selector: 'dst-cmp',
  template: ``,
})
export class Descendant {
  constructor(private ancestor: Ancestor) {}

  ngAfterViewChecked() {
    this.ancestor.text = 'updated';
  }
}

Running this produces the error:

Image alt

This demo matches this spec in the unit test suite:

initWithTemplate('<div id="Expressions: {{ a }}');

The interpolation instruction is relevant here, and we can spot it in the callstack:

Image alt

Interestingly, the error can sometimes appear even when updating the property in the ngOnInit hook:

@Component({
  selector: 'asr-cmp',
  template: `
    <dst-cmp></dst-cmp>
    <h3>Ancestor text: {{ text }}</h3>
  `,
})
export class Ancestor {
  text = 'initial';
}

@Component({
  selector: 'dst-cmp',
  template: ``,
})
export class Descendant {
  constructor(private ancestor: Ancestor) {}

  ngOnInit() {
    this.ancestor.text = 'updated';
  }
}

Again, we encounter the error:

Image alt

What might be unexpected – yet perfectly logical – is that the error disappears when we reorder elements in the Ancestor component template. Starting with this structure:

<h3>Ancestor text: {{text}}</h3>
<dst-cmp></dst-cmp>

And changing it to this:

<dst-cmp></dst-cmp>
<h3>Ancestor text: {{text}}</h3>

Essentially, we place <dst-cmp> before the interpolation display <h3>Ancestor text: {{text}}</h3>. Running this version results in no error.

The key lies in the compiled template function. Here's how the instructions appear for the original element order:

Image alt

Compare this with the reordered version:

Image alt

When <dst-cmp> appears before the interpolation Ancestor text: {{text}}, the ngOnInit hook executes within the Ancestor_Template function before Angular updates the DOM. This prevents the error. However, when <dst-cmp> comes after the interpolation, the ngOnInit hook runs through executeInitAndCheckHooks after the text property has already been processed by the template function, triggering the binding error.