title: javascript dynamic ellipsis text based on window size angular 2+
body: <p>So i have a chart (picture a gantt chart) with divs of various sizes, and there is text within them like this</p>
  
  <pre><code>&lt;div  #arrowWrap 
    [routerLink]="data.link"
    class="chart-item block-arrow"
    [ngClass]="{
      'disabled' : data.disabled,
      'enabled' : !data.disabled
    }"&gt;
       &lt;p #arrowText *ngIf="!squished"&gt;{{data.name}}&lt;/p&gt;
       &lt;p #arrowTextWithTooltip *ngIf="squished" matTooltip={{data.name}} class="ellipsis-text" &gt;{{data.name}}&lt;/p&gt;
  &lt;/div&gt;`
  </code></pre>
  
  <p>this div needs to maintain a width of 15% of the container to keep the continuity of the chart intact.</p>
  
  <p>I am trying to dynamically set a boolean <code>squished</code> based on when the <code>&lt;p&gt;</code> text overflows its container. This is the object component</p>
  
  <pre><code>import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';
  const ELLIPSIS_CLASS = 'ellipsis-text';
  
  @Component({
    selector: 'app-block-arrow',
    templateUrl: './block-arrow.component.html',
    styleUrls: ['./block-arrow.component.scss']
  })
  export class BlockArrowComponent implements OnInit {
    @ViewChild('arrowText') unsquishedP: ElementRef;
    @ViewChild('arrowTextWithTooltip') squishedP: ElementRef;
    @ViewChild('arrowWrap') outterDiv: ElementRef;
  
    @Input() data: any; // data to populate template
  
    maxSquishWidth = -1;  // viewport width that causes overflow of p
    squished = false;
  
  
    @Input() set viewportWidth(width) {
      if ( this.outterDiv) {
        this.determineSquished(this.outterDiv.nativeElement.children[0], width);
      }
    }
  
   determineSquished(el: any, width: number) {
      if (el) {
        const parent = el.offsetParent;
        // would only enter if the element overflowed it's container
        // the desired behavior is that text can fit onto 2 lines but not 3
        // for some reason the scroll height of the text is always 1 or 2 pixels longer
        // higher than the height of the parent div, thus the magic 6
        if ( el.scrollHeight &gt; parent.clientHeight + 6 ) {
          if (this.maxSquishWidth &lt; width) {
            this.maxSquishWidth = width;
          }
        }
  
        this.squished = (width &lt;= this.maxSquishWidth);
      }
    }
  }
  </code></pre>
  
  <p>the input property <code>width</code> is set using a service that creates an observable of event outputs from the window resize event</p>
  
  <pre><code>import { Injectable } from '@angular/core';
  import { Observable } from 'rxjs/Observable';
  import { fromEvent } from 'rxjs/Observable/fromEvent';
  import { map, pluck, distinctUntilChanged } from 'rxjs/operators';
  import { BehaviorSubject } from 'rxjs/BehaviorSubject';
  
  @Injectable({
    providedIn: 'root'
  })
  export class WindowService {
      public window = new BehaviorSubject(null);
  
      public width;
  
      constructor() {
          let windowSize = new BehaviorSubject(getWindowSize());
  
          this.width = windowSize.pipe(pluck('width'), distinctUntilChanged());   // &lt;- pluck as Observable&lt;number&gt; throwing errors
  
          fromEvent(window, 'resize').pipe(
            map(getWindowSize)
          ).subscribe(windowSize);
      }
  }
  
  function getWindowSize() {
      return {
          width: window.innerWidth
      };
  }
  </code></pre>
  
  <p>So all of this appears to be working as expected when the user resizes the window DOWN. The boolean flag get's set, the text gets squished to 2 lines (which still fits in the container div) and then it eventually overflows to 3 lines in which case the <code>minSquishedWidth</code> is set because the <code>&lt;p&gt;</code> scroll height is larger than the parent <code>&lt;div&gt;</code> height, which in turn sets the <code>squished</code> boolean to true, which in turn causes the 2nd <code>&lt;p&gt;</code> in my template to render with the tooltip and ellipsis class set.</p>
  
  <p>HOWEVER, when the user resizes the window larger, the text in divs toggles between being ellipsised (ellipsized??) to being on 3 lines and back again and it looks gross. This seems to be a result of the fact that if the user resizes the window too quickly, the window resize event doesn't fire until the window has stopped resizing for a moment, thus the <code>minSquishedWidth</code> can be set much lower than it actually should be. I have tried to correct for this by allowing the <code>minSquishedWidth</code> to increase as long as the overflow is still occurring on the way up, effectively "learning" it's correct position. And then if the user continues to resize without refreshing the page, the ellipsis behavior acts as expected...</p>
  
  <p>Anybody got an idea how to make it work with out having to "train it"? or perhaps by ensuring that the actual <code>minSquishedWidth</code> gets set the first time?</p>
  
  <p>The behavior of the component looks like this 
  :</p>
  
  <p><a href="https://i.stack.imgur.com/waVZQ.png" rel="nofollow noreferrer">starts as</a></p>
  
  <p><a href="https://i.stack.imgur.com/VTwgq.png" rel="nofollow noreferrer">then gets squished at some point on resize down</a></p>
  
  <p><a href="https://i.stack.imgur.com/RA98l.png" rel="nofollow noreferrer">then when the user resizes up, it toggles between this state and the first</a> as it "learns" the actual maxSquishWidth</p>