show Tooltip only when the ellipsis is active

Based on the Romain Deneau answer I created a small directive

@Directive({
  selector: '[matTooltip][appShowIfTruncated]'
})
export class ShowIfTruncatedDirective implements OnInit {
  constructor(
    private matTooltip: MatTooltip,
    private elementRef: ElementRef<HTMLElement>
  ) {
  }

  public ngOnInit(): void {
    // Wait for DOM update
    setTimeout(() => {
      const element = this.elementRef.nativeElement;
      this.matTooltip.disabled = element.scrollWidth <= element.clientWidth;
    });
  }
}

You can use it a little bit easier

<td mat-cell [attr.id]="hospital.organizational_id + '_hospitalname'" *matCellDef="let hospital">
  <div id="hospital_name" class="truncate" [matTooltip]="hospital.organization_name" appShowIfTruncated>
    {{hospital.organization_name}}
  </div>
</td>

Overflow of an element can be detected in JavaScript with this helper, using Angular ElementRef and a formula from this answer:

function isTextTruncated(element: ElementRef): boolean {
  const e = element.nativeElement;
  return e.scrollWidth > e.clientWidth;
}

Then, in your component, use it referencing the element with a "@ViewChild" property:

  @ViewChild('hospitalName') hospitalNameElement: ElementRef;

  isHospitalNameTruncated(): boolean {
    return isTextTruncated(this.hospitalNameElement);
  }

Finally, in the template, add the identifier #hospitalName and call isHospitalNameTruncated() to customize the tooltip text:

<td mat-cell [attr.id]="hospital.organizational_id + '_hospitalname'"
    *matCellDef="let hospital">
  <div id="hospital_name" #hospitalName class="truncate"
       [matTooltip]="isHospitalNameTruncated() ? hospital.organization_name : null ">
    {{hospital.organization_name}}
  </div>
</td>

Another improvement; Egor's answers is beautiful but unfortunately ngOnInit might not be enough. Examples:

  • When the element surrounding [matTooltip] is resizeable
  • When the text itself can be changed dynamically
  • When CSS selectors change the element padding/font-weight/font-size etc. and as a result truncates the text after initiation

Therefor the result is to bind to mouseenter (At that point, setTimeout becomes redundant):

import { Directive, ElementRef, HostListener } from '@angular/core';
import { MatTooltip } from '@angular/material';

@Directive({
  selector: '[matTooltip][showIfTruncated]'
})
export class ShowIfTruncatedDirective {
  
  constructor(
    private matTooltip: MatTooltip,
    private elementRef: ElementRef<HTMLElement>
  ) {}

  @HostListener('mouseenter', ['$event'])
  setTooltipState() {
      const element = this.elementRef.nativeElement;
      this.matTooltip.disabled = element.scrollWidth <= element.clientWidth;
  }
}