Search code examples
angularangular-material

ExpressionChangedAfterItHasBeenCheckedError when dynamically assigning value to matTooltip


When dynamically assigning value to matTooltip I get this error ExpressionChangedAfterItHasBeenCheckedError.
I tried fixing it using detectChanges() but then I get this error Maximum call stack size exceeded.

Requirement: I need to show tooltip when text is truncated.

Html

<div class="container">
  <p *ngFor="let each of tooltipsData" #element [matTooltip]="isTextTruncated(element) ? each : null">{{each}}</p>
</div>

Typescript

tooltipsData = [
 "Lorem Ipsum",
 "Lorem Ipsum is simply dummy text",
 "Lorem",
 "Lorem Ipsum is simply dummy text of the printing and typesetting"
];

isTextTruncated(element: any) {
 // this.cdr.detectChanges();
 return element.scrollWidth > element.clientWidth;
}

Stackblitz Example


Solution

  • TL;DR - call detectChanges() in ngAfterViewInit()

    Example

    import { Component, AfterViewInit, ChangeDetectorRef } from '@angular/core';
    ...
    constructor(private cdr: ChangeDetectorRef) { }
    ...
    ngAfterViewInit() {
     this.cdr.detectChanges();
    }
    

    Explanation

    Angular runs list of lifecycle hooks starting from ngOnInit to ngAfterViewInit. In this process angular stores previous values and compare with current value (e.g., In ngOnInit when x is false and in ngAfterViewInit x is true), when value is changed while lifecycle hooks are running then this error occurs Expression...Previous value: 'message: false'. Current value: 'message: true'.

    My scenario

    I am displaying tooltip based on DOM manipulation (when text is truncated), So in ngOnInit no elements are loaded in DOM, So initially value is null and in ngAfterViewInit elements are loaded and tooltip value changed from null to Lorem Ipsum is simply dummy text. So we need to tell angular to detect changes in ngAfterViewInit.

    Other Scenario Async API

    Update

    The above solution works in example stackblitz, but in my real angular project it doesn't work because I get tooltip value from Async API.

    So I called detectChanges() after API is completed.