2
votes

I have an array of items displayed in a list in a component. One of the properties of the item is a description field which can range from one word to a couple of sentences long.

When the description is too long I would like to truncate it and display a button that would display the full text when clicked. The condition for whether to truncate and display the button is based on this function that returns a boolean:

checkOverflow(element) {
  if (element.offsetHeight < element.scrollHeight ||
    element.offsetWidth < element.scrollWidth) {
    return true;
  } else {
    return false;
  }
}

I then use this function from the template side of the component like this (one small part of the ngFor):

 <p #refDescription
    class="item-description--max-height"
    [class.fade--read-more]="checkOverflow(refDescription)">
        {{item.description}}
 </p>
 <button class="button button--read-more" *ngIf="checkOverflow(refDescription">Read more</button>

The CSS being used here is simply:

.item-description--max-height {
  font-size: 20px;
  @include font-fira-sans-italic;
  line-height: 28px;
  margin-bottom: 0;  
  max-height: 2.9em;
  overflow: hidden;
}

.fade--read-more {
  height: 2.9em; /* exactly two lines */
}

At the moment the behaviour is very hit-and-miss and I get an error in the console:

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: false'. Current value: 'ngIf: true'.

I believe I am on the wrong track with binding the class to the function - any advice much appreciated. I am using Angular version 7.

Thanks.

1
did you fix it already ?Manoj

1 Answers

1
votes

Typically this error caused by methods invoked inside template.

Try to move checkOverflow() calls into component:

// use view children to get references to dom elements
@ViewChildren('refDescription') descRef: QueryList<ElementRef>;

ngAfterViewInit(): void {
  // wait when dom rendered
  setTimeout(() => {
    // get dom elements
    const elements = this.descRef.toArray().map(i => i.nativeElement);
    // check overflow and put result into 'overflow' property
    elements.forEach((el, index) => {
      this.items[index]['overflow'] = this.isOverflow(el); 
    });
  });
}

Update template accordingly:

<p #refDescription
    class="item-description--max-height"
    [class.fade--read-more]="item.overflow">
        {{item.description}}
 </p>
 <button class="button button--read-more" *ngIf="item.overflow">Read more</button>