Brian Love
Google Developer Expert in Angular, software engineer and skier located in Denver, CO

Ellipsis Directive

Reading time ~3 minutes

Learn how to easily truncate and append an ellipsis to content using an Angular directive.

Goals

The goals for our ellipsis directive are:

  • A simple attribute directive that can be applied to an element with a specified fixed height.
  • Avoid overflowing the text content beyond the specified fixed height.
  • If the text is overflowing, remove the necessary text and append an ellipsis until the text fits within the specified height.

ElementRef

The first concept we need to understand is Angular’s ElementRef instance when using an attribute directive. We can access this via dependency injection in our attribute directive’s constructor() function.

The nativeElement property on the ElementRef class instance enables us to access the native document object model (DOM) element in the context of the browser. Once we have access to the element, we can determine if the element’s scrollHeight property value exceeds the element’s clientHeight property value.

Platform Specific

It’s important to note that the strategy that we’ll implement here will be platform specific. To clarify, Angular is not just a framework for building single-page applications in the context of a browser. Rather, Angular can be used for building applications in any context, but is most often used in the context of a browser, Node.js, electron for building desktop applications, and either NativeScript or Ionic for building hybrid mobile applications.

In summary, we’ll use the isPlatformBrowser() function that is available in the @angular/common module for verifying that the application is indeed executing in the context of the browser.

The EllipsisDirective

Here is the full source code for the Angular attribute directive:

@Directive({
  selector: '[appEllipsis]'
})
export class EllipsisDirective implements AfterViewInit {
  /** The native HTMLElement. */
  private get el(): HTMLElement {
    return this.elementRef.nativeElement;
  }

  /** The original innerText; */
  private innerText: string;

  constructor(
    private readonly elementRef: ElementRef,
    @Inject(PLATFORM_ID) private readonly platformId
  ) {}

  public ngAfterViewInit(): void {
    this.truncate();
  }

  @HostListener('window:resize')
  private onWindowResize() {
    this.truncate();
  }

  private truncate(): void {
    // verify execution context is the browser platform
    if (!isPlatformBrowser(this.platformId)) {
      return;
    }

    // store the original innerText
    if (this.innerText === undefined) {
      this.innerText = this.el.innerText.trim();
    }

    // reset the innerText
    this.el.innerText = this.innerText;

    // truncate the text and append the ellipsis
    const text = this.innerText.split(' ');
    while (text.length > 0 && this.el.scrollHeight > this.el.clientHeight) {
      text.pop();
      this.el.innerText = `${text.join(' ')}…`;
    }
  }
}

Let’s dive into the the implementation of the EllipsisDirective:

  • First, we have a handy el property accessor for accessing the nativeElement property on the injected ElementRef class instance. Note that the return type for this is a generic HTMLElement. This is necessary so we have intellisense and type checking when accessing the innerText property of the element.
  • Next, the innerText class property is simple a string in which we will store the original text within the element that the attribute directive is applied to.
  • In the constructor() function we are injecting both the ElementRef class instance as well as the platformId using the PLATFORM_ID injection token.
  • The directive implements the AfterViewInit lifecycle interface and it’s associated ngAfterViewInit() lifecycle method. Once the view has been initialized, we can truncate the text based on the element’s scrollHeight and clientHeight values. We’ll see this below in the truncate method.
  • We’ll invoke the truncate() method both after the view has been initialized as well as when the browser window has been resized.
  • Using the HostListener() decorator we can listen for the resize event to occur on the global window object, and then truncate the text as necessary. This is especially important if the width of the element is not fixed.
  • Finally, within the truncate() method we’ll first verify that we are executing in the context of a browser. Then, we’ll store the initial value of the innerText property so that if we need to truncate the text later based on the window being resized we can do so. We then truncate the text and append the ellipsis by comparing the value of the element’s scrollHeight to the clientHeight. We’ll continue to remove words until the content fits within the specified height of the element.

Demo

Check out the following stackblitz with an example of the EllipsisDirective:

Brian Love

Hi, I'm Brian. I am interested in TypeScript, Angular and Node.js. I'm married to my best friend Bonnie, I live in Denver and I ski (a lot).