Search code examples
angularangular-pipe

Need input on angular pipes


Long strings on site must have ellipsis after they reach a certain count. I created the following angular pipe

export class FormatString implements PipeTransform {

 transform(widgetString1: string): string {
     const maxWords = 20;
     const ellipsis = '...';
     if (widgetString1.length > 10) {
         return widgetString1.substring(0, maxWords) + ellipsis;
     } else {
         return widgetString1;
     }
    }

The pipe is used in HTML like so

<div class="text-nowrap ellipsis" *ngIf="max[i]=== 65">
{{item.message | formatString}}

It works fine, I want to add more functionality to the pipe. Once the user clicks the text, the ellipsis should go away and the full text should be shown. How should I modify the pipe?

BTW using pipes would be ideal so I can use it everywhere in the application


Solution

  • One way to do it configure your Pipe to accept length of chars as input, and you decide in your template whether you want to trim the text to lesser length or pass the size of 'name' if you want to retain full text.

    @Pipe({name: 'formatString'})
    export class FormatString implements PipeTransform {
     transform(text: string, trimTo: number): string {
         const ellipsis = '...';
         if (trimTo && text.length > trimTo) {
             return text.substring(0, trimTo) + ellipsis;
         } else {
             return text;
         }
      }
    }    
    

    In your template, you can decide what will the value of parameter to pass for trim size. In example below, clicking on div will display full value of 'name' and clicking again will display trimmed text.

    <div class="text-nowrap ellipsis" 
             (click)="nameDiv.displayFullText = !nameDiv.displayFullText" #nameDiv>
    
      {{name | formatString:(nameDiv.displayFullText ? name.length : 5) }}  
    
    </div>
    

    Now, you have multiple such variables like 'name' in your template - you could add an attribute in your component for each of them to track whether you want to display full length or trimmed text. But that can become unmanageable.

    What I have done above is use a template reference variable #nameDiv and stored the flag in div element. This too can get unmanageable depending on how you see it.

    I am thinking that you may be better of defining a directive for your use case.

    We can define an attribute directive, lets call it trim, and provide it two inputs: displayText to display, and maxCharsToDisplay for max number of chars to display without ellipsis.

    For example, your template will be something like this:

    <h1 ellipsisText [displayText]="name" [maxCharsToDisplay]='4'>
    </h1>
    

    Here is implementation of directive:

    @Directive({
      selector: '[ellipsisText]',
    })
    export class EllipsisTextDirective implements OnInit {
    
        @Input()
        displayText: string = "";
    
        @Input()
        maxCharsToDisplay: number;
    
        showFullText = false;
        ellipsis = '...';
    
        constructor(private elr:ElementRef){
        }
    
        // Display initial text in trimmed form
        ngOnInit() {
           this.elr.nativeElement.innerHTML = this.displayText;
           this.trimText();
        }
    
        // On click - toggle between full text display and trimmed text display
        @HostListener('click') onClick() {
          this.showFullText = !this.showFullText;
    
          if (this.showFullText) {
            this.fullText();
          } else {
            this.trimText();
          }
        }
    
        fullText() {
            this.elr.nativeElement.innerHTML = this.displayText;
        }
        trimText() {
          if (this.displayText.length > this.maxCharsToDisplay) {
            this.elr.nativeElement.innerHTML = 
                 this.displayText.substring(0, this.maxCharsToDisplay) + this.ellipsis;
          }
        }
    }