Code Buckets

Buckets of code

Angular

Angular pipe to truncate text to the nearest whole word

The problem

I want to truncate text in Angular 5 using a pipe. I don’t just want to chop the text up inbetween words. I want to truncate to the nearest whole word. And I want a ellipses on. I want it all. So the following text as an example

Mind precedes all mental states. Mind is their chief; they are all mind-wrought. If with an impure mind a person speaks or acts suffering follows him like the wheel that follows the foot of the ox.

Truncated to a length of not more than 150 would be

Mind precedes all mental states. Mind is their chief; they are all mind-wrought. If with an impure mind a person speaks or acts suffering follows…

I want that – and as luck would have it is actually a nice quote. Purely coincidental of course.

Starting off

First let’s use angular cli scaffolding to get us the base pipe. So open the command line and run

ng g pipe TruncateText

Or if you prefer

ng generate pipe TruncateText

And the following js file pops out

Import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'truncateText'
})
export class TruncateTextPipe implements PipeTransform {

    transform(value: any, args?: any): any {
       return null;
  }
}

Nice start. The pipe metadata tells us that to use it in the html page we will be using the truncatetext tag. It’s pretty obvious where our implementation is going to be.

The Implementation

So filling out the implementation gives us

import { Pipe, PipeTransform } from '@angular/core';


@Pipe({
  name: 'truncatetext'
})

export class TruncateTextPipe implements PipeTransform {

  transform(value: string, length: number): string {
    const biggestWord = 50;
    const elipses = "...";

    if(typeof value === "undefined") return value;
    if(value.length <= length) return value;

    //.. truncate to about correct lenght
    let truncatedText = value.slice(0, length + biggestWord);

    //.. now nibble ends till correct length
    while (truncatedText.length > length - elipses.length) {

        let lastSpace = truncatedText.lastIndexOf(" ");
        if(lastSpace === -1) break;
        truncatedText = truncatedText.slice(0, lastSpace).replace(/[!,.?;:]$/, '');

    };

   return truncatedText + elipses;

  }
}

So a bit at a time. The signature has changed to

transform(value: string, length: number): string {

Value is the string that is to be truncated. Length is our target (maximum) length. It returns a string which is the truncated text. There is a bit of parameter checking then

let truncatedText = value.slice(0, length + biggestWord);

I’ve taken the view that the biggest word I’m likely to deal with is

Pneumonoultramicroscopicsilicovolcanoconiosis

So it’s 45 letters plus 5 for any spelling mistakes for a 50 character margin. (I’m ignoring the 189,819 letter protein name that someone unearthed – I guess biochemists need to use this Angular filter with caution).

So I truncate at my desired length plus 50 then nibble backwards at each space

while (truncatedText.length > length - elipses.length) {
  let lastSpace = truncatedText.lastIndexOf(" ");
  if(lastSpace === -1) break;
  truncatedText = truncatedText.slice(0, lastSpace)replace(/[!,.?;:]$/, '');
 }

Until I get to my length then glue on the ellipse and return it out. The only other thing Is

.replace(/[!,.?]$/, '');

A sprinkle of regex to nibble off any comma, question mark etc.. What I don’t want is a result like

Mind precedes all mental states. Mind is their chief;…

It just looks weird.; I want

Mind precedes all mental states. Mind is their chief…

Much better.

Using the pipe

So to actually use the pipe in the component we need to declare it in the app.module e.g.

@NgModule({
  declarations: [
    //.. other declarations,
    TruncateTextPipe
  ],
  imports: [
  //.. something here
  ]
  ),
  BrowserModule,
  FormsModule,
  HttpClientModule
  ],
  providers: [
  //.. my services etc..
  ],
  bootstrap: [AppComponent]
  })
export class AppModule { }

If you’ve use the angular cli command then that would have been done for you. Then to use in the component it is simply

<p [innerHTML]="quote.text | truncatetext:150" ></p>

And then that is it – my text is truncated in my angular app and looks something like this

Lovely filter; lovely quote.

Variant for truncating to a small length

It’s an edge case for me but there is the case where the filter is set to a (stupidly) small value e.g.

<p [innerHTML]="quote.text | truncatetext:5" ></p>

Taking into account the ellipses, the logic would be that the entire string evaporates to just the ellipse or perhaps nothing. I don’t really want that so this filter will return the first word with an ellipse as the minimum return value i.e.

Mind…

If you want the behaviour where the output strictly adheres to the length or less then it would be something like ..

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'truncatetext'
})

export class TruncateTextPipe implements PipeTransform {
  transform(value: string, length: number): string {

    const biggestWord = 50;
    const elipses = "...";

    if(typeof value === "undefined") return value;
    if(value.length <= length) return value;
    if(length < elipses.length) return '';

    //.. truncate to about correct lenght
    let truncatedText = value.slice(0, length + biggestWord);
  
    //.. now nibble ends till correct length
    while (truncatedText.length > length - elipses.length) {

     let lastSpace = truncatedText.lastIndexOf(" ");

     if(lastSpace === -1) {
       truncatedText = ""
       break;
     }
    truncatedText = truncatedText.slice(0, lastSpace).replace(/[!,.?]$/,'');
    };
    return truncatedText + elipses;
  }
}

So it will return the length or less in all cases – if you really want that.

Demo App

As ever the code is on my github site here. It’s part of a bigger app and associated API that generates Buddhist quotes from the Pail Canon. All work in progress but I quite like it.

Useful Links

https://angular.io/guide/pipes
Official documentation for angular pipes. Useful

https://en.wikipedia.org/wiki/Longest_word_in_English
Biggest words in English for edge cases and general marvelment

https://www.accesstoinsight.org/tipitaka/kn/dhp/dhp.01.budd.html
The example text is the first verse of the Dhammapada -original translations by Acharya Buddharakkhita. The first verses are all in pairs so the example text with its partner is ..

Mind precedes all mental states. Mind is their chief; they are all mind-wrought. If with an impure mind a person speaks or acts suffering follows him like the wheel that follows the foot of the ox.

Mind precedes all mental states. Mind is their chief; they are all mind-wrought. If with a pure mind a person speaks or acts happiness follows him like his never-departing shadow.

Tremendous – one of my favourites.

2 COMMENTS

  1. Any tips for using your above code to instead, add ellipsis to center of long string? Leaving the beginning and end visible.

  2. Hi, thank you so much, there was a minor bug when the string is null, but it solves in one line: if (value === null) return ”;

LEAVE A RESPONSE

Your email address will not be published. Required fields are marked *