Angular 2 Read More Directive

27,668

Solution 1

I think you'll need a Component rather then Directive. Components makes more sense since you need to add Read more button/link, i.e. update DOM.

@Component({
    selector: 'read-more',
    template: `
        <div [class.collapsed]="isCollapsed">
            <ng-content></ng-content>
        </div>
        <div (click)="isCollapsed = !isCollapsed">Read more</div>
    `,
    styles: [`
        div.collapsed {
            height: 250px;
            overflow: hidden;
        }
    `]
})

export class ReadMoreComponent {
    isCollapsed = true;
}

Usage:

<read-more>
   <!-- you HTML goes here -->
</read-more>

Solution 2

I made a version that uses character length rather than div size.

import { Component, Input, ElementRef, OnChanges} from '@angular/core';

@Component({    
    selector: 'read-more',
    template: `
        <div [innerHTML]="currentText">
        </div>
            <a [class.hidden]="hideToggle" (click)="toggleView()">Read {{isCollapsed? 'more':'less'}}</a>
    `
})

export class ReadMoreComponent implements OnChanges {
    @Input() text: string;
    @Input() maxLength: number = 100;
    currentText: string;
    hideToggle: boolean = true;

    public isCollapsed: boolean = true;

    constructor(private elementRef: ElementRef) {

    }
    toggleView() {
        this.isCollapsed = !this.isCollapsed;
        this.determineView();
    }
    determineView() {
        if (!this.text || this.text.length <= this.maxLength) {
            this.currentText = this.text;
            this.isCollapsed = false;
            this.hideToggle = true;
            return;
        }
        this.hideToggle = false;
        if (this.isCollapsed == true) {
            this.currentText = this.text.substring(0, this.maxLength) + "...";
        } else if(this.isCollapsed == false)  {
            this.currentText = this.text;
        }

    }
    ngOnChanges() {
        this.determineView();       
    }
}

Usage:

<read-more [text]="text" [maxLength]="100"></read-more>

Solution 3

With the help from Andzhik I am able to build the below component that meets my requirements.

import { Component, Input, ElementRef, AfterViewInit } from '@angular/core';

@Component({
    selector: 'read-more',
    template: `
        <div [innerHTML]="text" [class.collapsed]="isCollapsed" [style.height]="isCollapsed ? maxHeight+'px' : 'auto'">
        </div>
            <a *ngIf="isCollapsable" (click)="isCollapsed =! isCollapsed">Read {{isCollapsed? 'more':'less'}}</a>
    `,
    styles: [`
        div.collapsed {
            overflow: hidden;
        }
    `]
})
export class ReadMoreComponent implements AfterViewInit {

    //the text that need to be put in the container
    @Input() text: string;

    //maximum height of the container
    @Input() maxHeight: number = 100;

    //set these to false to get the height of the expended container 
    public isCollapsed: boolean = false;
    public isCollapsable: boolean = false;

    constructor(private elementRef: ElementRef) {
    }

    ngAfterViewInit() {
        let currentHeight = this.elementRef.nativeElement.getElementsByTagName('div')[0].offsetHeight;
       //collapsable only if the contents make container exceed the max height
        if (currentHeight > this.maxHeight) {
            this.isCollapsed = true;
            this.isCollapsable = true;
        }
    }
}

Usage:

<read-more [text]="details" [maxHeight]="250"></read-more>

Solution 4

import { Component, Input,OnChanges} from '@angular/core';
@Component({    
    selector: 'read-more',
    template: `
        <div [innerHTML]="currentText"></div>
        <span *ngIf="showToggleButton">
            <a (click)="toggleView()">Read {{isCollapsed? 'more':'less'}}</a>
        </span>`
})

export class ReadMoreDirective implements OnChanges {

    @Input('text') text: string;
    @Input('maxLength') maxLength: number = 100;
    @Input('showToggleButton')showToggleButton:boolean;

    currentText: string;

    public isCollapsed: boolean = true;

    constructor(
        //private elementRef: ElementRef
    ) {

    }
    toggleView() {
        this.isCollapsed = !this.isCollapsed;
        this.determineView();
    }

    determineView() {

        if (this.text.length <= this.maxLength) {
            this.currentText = this.text;
            this.isCollapsed = false;
            return;
        }

        if (this.isCollapsed == true) {
            this.currentText = this.text.substring(0, this.maxLength) + "...";
        } else if(this.isCollapsed == false)  {
            this.currentText = this.text;
        }

    }

    ngOnChanges() {
        if(!this.validateSource(this.text)) {
            //throw 'Source must be a string.';
            console.error('Source must be a string.');
        }
        else{
            this.determineView();
        }
    }

    validateSource(s) {
        if(typeof s !== 'string') {
            return false;
        } else {
            return true;
        }
    }
}

and usage

<read-more [text]="this is test text" [maxLength]="10" [showToggleButton]="true"></read-more>

Solution 5

Again i solved these types of problem with dynamic data and full controlling.

 <div class="Basic-Info-para">
   <p>
     <span *ngIf="personalBasicModel.professionalSummary.length>200" id="dots"> 
         {{personalBasicModel.professionalSummary | slice:0:200}} ...
    </span>
     <span id="more">{{personalBasicModel.professionalSummary }}
</span>
 </p>
</div> 

Here , personalBasicModel.professionalSummary contain string . like any text.
slice:0:200 = use slice pipe for splice string with 200 character length . you can be change length according your requirement. id="dots" & id="more" two important thing.

<div class="Basic-Info-SeeMore">
            <button class="SeeMore"(click)="showMore(paasValueOn_SeeMoreBtn)">
                {{showLess_More}}
            </button>
        </div>

Here we define a button with dynamic text (see more and see less ) with click event.

//---------------------------------- ts file -----------------------------------//

Define variable

showLess_More : string = "SEE MORE...";
paasValueOn_SeeMoreBtn : boolean = true;

Event(Method) fire when user click on see more button

 showMore(data:boolean){
    if(data){
      $("#dots").css('display', 'none');
      $("#more").css('display', 'inline');
      this.showLess_More = "SEE LESS ...";
      this.paasValueOn_SeeMoreBtn = false;
    }else{
      $("#dots").css('display', 'inline');
      $("#more").css('display', 'none');
      this.showLess_More = "SEE MORE...";
      this.paasValueOn_SeeMoreBtn = true;

    }

  }
Share:
27,668

Related videos on Youtube

Naveed Ahmed
Author by

Naveed Ahmed

Updated on July 09, 2022

Comments

  • Naveed Ahmed
    Naveed Ahmed almost 2 years

    I need to build a readmore directive in Angular2. What this directive will do is for collapse and expand long blocks of text with "Read more" and "Close" links. Not on the basis of the character count but on the basis of the specified max height.

    <div read-more [maxHeight]="250px" [innerHTML]="item.details">
    </div>
    

    Can anyone please guide what would be the most reliable way to get/set the height of the element for this specific case.

    Any guidelines on how this specific directive could be implemented would also be highly appreciated.

    I need to build something like this https://github.com/jedfoster/Readmore.js

    Solution:

    With the help from Andzhik I am able to build the below component that meets my requirements.

    import { Component, Input, ElementRef, AfterViewInit } from '@angular/core';
    
    @Component({
        selector: 'read-more',
        template: `
            <div [innerHTML]="text" [class.collapsed]="isCollapsed" [style.height]="isCollapsed ? maxHeight+'px' : 'auto'">
            </div>
                <a *ngIf="isCollapsable" (click)="isCollapsed =! isCollapsed">Read {{isCollapsed? 'more':'less'}}</a>
        `,
        styles: [`
            div.collapsed {
                overflow: hidden;
            }
        `]
    })
    export class ReadMoreComponent implements AfterViewInit {
    
        //the text that need to be put in the container
        @Input() text: string;
    
        //maximum height of the container
        @Input() maxHeight: number = 100;
    
        //set these to false to get the height of the expended container 
        public isCollapsed: boolean = false;
        public isCollapsable: boolean = false;
    
        constructor(private elementRef: ElementRef) {
        }
    
        ngAfterViewInit() {
            let currentHeight = this.elementRef.nativeElement.getElementsByTagName('div')[0].offsetHeight;
           //collapsable only if the contents make container exceed the max height
            if (currentHeight > this.maxHeight) {
                this.isCollapsed = true;
                this.isCollapsable = true;
            }
        }
    }
    

    Usage:

    <read-more [text]="details" [maxHeight]="250"></read-more>
    

    If there could be any improvements, please feel free to suggest.

    • chenk
      chenk over 7 years
      Find currentHeight inside the setTimeout(_ => { ..... }) would eliminate some window resizing issue while angular running change detections. see stackoverflow.com/questions/38930183/…
  • Naveed Ahmed
    Naveed Ahmed almost 8 years
    Thank you so much Andzhik for your reply. I made some changes to the component: template : ` <div [innerHTML]="text" [class.collapsed]="isCollapsed"> </div> <div (click)="isCollapsed =! isCollapsed">Read more</div> ` styles: [` div.collapsed { height: 150px; overflow: hidden; } `] I also added @Input() text: string; but I still need a way to get the height of the div containing text so that on the basis of which I can decide whether to show or hide Read more link.
  • Andrei Zhytkevich
    Andrei Zhytkevich almost 8 years
    You can use ElementRef ngcourse.rangle.io/handout/advanced-components/elementref.ht‌​ml to access directly DOM element representing your Component. PS: if you are satisfied with my original answer, please accept it, thanks.
  • Naveed Ahmed
    Naveed Ahmed almost 8 years
    I tried that but when I log on console in constructor or in ngAfterContentChecked it returns undefined console.log(this.el.nativeElement.height); I also tried @HostBinding('style.height') private height: string; but its also undefined.
  • Andrei Zhytkevich
    Andrei Zhytkevich almost 8 years
    Looks like the better lifecycle hook will ngAfterViewInit and you need to get this.el.nativeElement.offsetHeight instead of ...height. height is a member of `this.el.nativeElement.style' and has no value at that point.
  • Naveed Ahmed
    Naveed Ahmed almost 8 years
    Thank you so much Andzhik you were very helpful, I have posted my final read more component below, please suggest if there can be any improvements.
  • Rodney
    Rodney over 7 years
    Thanks, that was just what I needed!
  • Naveed Ahmed
    Naveed Ahmed over 7 years
    What about contents with html tags?
  • jugg1es
    jugg1es over 7 years
    i think it should work with html. It's using [innerHTML] directive
  • Yasir
    Yasir almost 7 years
    @jugg1es hi, the button read less still appear when word count is less than 100.. also can we have animation for expand and collapse ! how to do that
  • jugg1es
    jugg1es almost 7 years
    @DavidBracoly stackoverflow is meant to answer questions, not respond to feature requests like github. If you want to add those features, it really shouldn't be that hard to implement the changes you want. That said, if you want those features added, ill give you my paypal account and you can forward me $200 and I'll see what I can do :)
  • Yasir
    Yasir almost 7 years
    thanx for the help
  • BEJGAM SHIVA PRASAD
    BEJGAM SHIVA PRASAD about 6 years
    Small Change in template... add *ngIf to anchor...*ngIf="text.length >= this.maxLength"
  • JinWu
    JinWu about 4 years
    [maxLength]="100" is a good point, which reminders me to set a max-height rather than a fixed height, which could solve text not vertical align middle issue.
  • Daniel Masih
    Daniel Masih almost 4 years
    Best answer without CSS