Better know a framework #22: bind and attached

#1

Base doc:
https://aurelia.io/docs/templating/html-behaviors#html-behavior-lifecycle

  • bind() typically happens after view and view model bindings have been created and bound. At this point, view has also been created and added to the host element, but the host element has not been connected to the document yet.
  • attached() typically happens after the host element of a custom element/attribute has been connected to the document.

During bind(), all bindings have been bound:

  • view model has access to the view elements via ref bindings. This is a great time to do heavy DOM modification setup, as it doesn’t trigger browser to re-layout or re-render.
  • parent data has been passed to child via bound binding, it’s good, and early enough to initiate data related setup. For examples: create third party utilities instances, setup observer on elements, create dynamic bindings etc…
  • view has not been connected to the document, so anything that requires real element sizing will not work. Typical apis: window.getComputedStyle, element.getBoundingClientRect(), element.offsetHeight, element.clientHeight etc…

During attached(), all bindings have been bound and view has been connected to the document:

  • animation can be run, when there is need for visual feedback.
  • element sizing can be used. This is great for setup that needs some specific calculation, such as canvas rendering, text/code editor resizing.
  • document.querySelector(...), jQuery(...) can be used, it can be used for some third party library that relies on selctor

What do you use?

4 Likes

#2

Some examples (not nearly exhaustive, but these would be fairly common):

Bind()

  • Initialize models in a component based on values passed to the bindings (vCurrent)
@customElement('product-details')
export class ProductDetails {
  public static inject = [ProductService, CompositionTransaction];

  @bindable public productId: number;

  public product: ProductModel;
  public service: ProductService;
  public notifier: CompositionTransactionNotifier;

  constructor(productService, compositionTransaction) {
    this.service = productService;
    this.notifier = compositionTransaction.enlist();
  }

  public bind(): void {
    this.service.getProduct({ id: this.productId })
      .then(product => {
        this.product = product;
        this.notifier.done();
      });
  }
}
  • Initialize models in a component based on values passed to the bindings (vNext)
@customElement('product-details')
export class ProductDetails {
  public static inject = [ProductService];

  @bindable public productId: number;

  public product: ProductModel;
  public service: ProductService;

  constructor(productService) {
    this.service = productService;
  }

  public async binding(): Promise<void> {
    // Promises returned from binding and unbinding are awaited
    // before the lifecycle continues in vNext
    this.product = await this.service.getProduct({ id: this.productId });
  }
}
  • Add/remove event listeners (also fine during attached()), vCurrent (vNext is identical here except it’s binding() instead of bind())
@customElement('draggable-widget')
export class DraggableWidget {
  public static inject = [Element];

  constructor(
    public element: Element
  ) {}

  public bind(): void {
    this.element.addEventListener('mousedown', this.handleMouseDown);
  }

  public unbind(): void {
    this.element.removeEventListener('mousedown', this.handleMouseDown);
  }

  private readonly handleMouseDown = (ev: MouseEvent) => {
    window.addEventListener('mousemove', this.handleMouseMove);
    window.addEventListener('mouseup', this.handleMouseUp);
  };

  private readonly handleMouseUp = (ev: MouseEvent) => {
    window.removeEventListener('mousemove', this.handleMouseMove);
    window.removeEventListener('mouseup', this.handleMouseUp);
  };

  private readonly handleMouseMove = (ev: MouseEvent) => {
    // magic to move the element
  };
}

Attached()

  • Initialize 3rd party UI plugins that require the DOM to be attached and ready
@customElement('tabs-container')
export class TabsContainer {
  public static inject = [Element];

  constructor(
    public element: Element
  ) {}

  public attached(): void {
    $(this.element).init(...); // some jQuery/bootstrap stuff
  }

  public detached(): void {
    $(this.element).destroy(...); // some jQuery/bootstrap stuff
  }
}
2 Likes