Wrapping a third party component that changes DOM

Yesterday, I had a go at using textFit to autosize the text inside a div to fill it completely. The challenge was that textFit() changes the DOM, which makes the Aurelia databinding stop working. I ended up finding a solution, but it seems inelegant, and I can’t help thinking there must be a much simpler way to do this.

This is how I did it:
song-part.ts

import { bindable, autoinject, TaskQueue, LogManager, noView, ViewFactory, ViewCompiler, ViewSlot, Container, ViewResources } from 'aurelia-framework';
import textFit from 'textfit';
import './song-part.scss'

const logger = LogManager.getLogger('song-part');

@noView()
@autoinject()
export class SongPart {
  viewSlot: ViewSlot;
  container: Container;
  viewFactory: ViewFactory;
  lineContainer: Element;
  taskQueue: TaskQueue;
  @bindable lines: any;

  constructor(taskQueue: TaskQueue, viewCompiler: ViewCompiler, viewSlot: ViewSlot, container: Container, viewResources: ViewResources) {
    this.taskQueue = taskQueue;
    this.viewSlot = viewSlot;
    this.container = container;
    this.viewFactory = viewCompiler.compile(
      '<template>'
      + '  <div ref="lineContainer" class="line-container">'
      + '    <span repeat.for="line of lines">${line}<br if.bind="!$last"></span>'
      + '  </div>'
      + '</template>', viewResources);
  }

  linesChanged(newValue: any) {
    logger.debug('linesChanged', newValue);
    let view = this.viewFactory.create(this.container);
    view.bind(this);
    this.viewSlot.removeAll();
    this.viewSlot.add(view);
    this.viewSlot.attached();

    this.taskQueue.queueMicroTask(() => {
      textFit(this.lineContainer, { alignHoriz: true, alignVert: true });
    });
  }
}

song-part.scss

.line-container {
  width: 100vw;
  height: 100vh;
}

Use custom attribute to bind the model, then manually control html.

<div class="line-container" text-fit.bind="lines"></div>

text-fit.js

import {inject} from 'aurelia-framework';
import textFit from 'textfit';

@inject(Element)
export class TextFitCustomAttribute {
  constructor(element) {
    this.element = element;
  }

  valueChanged(newValue, oldValue) {
    if (!this.live) return;
    this._buildHtml(newValue);
  }

  _buildHtml(value) {
    if (value && value.join) {
      this.element.innerHTML = value.join('<br>');
    } else {
      this.element.innerHTML = value || '';
    }
    textFit(this.element, { alignHoriz: true, alignVert: true });
  }

  attached() {
    this.live = true;
    this._buildHtml(this.value);
  }

  detached() {
    this.live = false;
  }
}

You can go even fancier if you want to. Using dynamic-options-binding, you can support
text-fit="value: lines; options.bind: {alignHoriz: true, alignVert: true}".

http://aurelia.io/docs/templating/custom-attributes#dynamic-options-binding