Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Aurelia attached triggers before repeat.for

I'm trying to setup certain logic in Aurelia that would affect DOM nodes looped by repeat.for. If I understand the documentation correctly, the attached() callback of the view is called after DOM rendering and is the place put this kind of logic in.

The problem is that the attached() callback seems to be fired before the repeat.for binding is complete, leaving me with only a partially rendered dom.

In order to illustrate the problem:

I have a custom element containing:

<template>
    <ul>
      <li repeat.for="thing of things"></li>
    </ul>
</template>

Once attached is called(), I would expect having a rendered DOM containing all the li elements, instead. A simple dump of the dom shows an empty

How can is implement a callback that gets access to those li nodes?

like image 425
Sven van de Scheur Avatar asked Mar 14 '23 11:03

Sven van de Scheur


2 Answers

attached is called when the component's DOM element is "attached" to the DOM. There may be child components such as repeated templates that are further down in the queue to be rendered, the easiest thing to do would be to put your logic at the bottom of the queue:

import {inject, TaskQueue} from 'aurelia-framework';

@inject(TaskQueue)
export class MyComponent {
  constructor(taskQueue) {
    this.taskQueue = taskQueue;
  }

  doSomethingAfterRepeatIsRendered() {
    // your logic...
  }

  attached() {
    this.taskQueue.queueMicroTask({
      call: () => this.doSomethingAfterRepeatIsRendered();
    });
  }
}

There are better approaches than this, but I would need to know more about what kind of work you need to do with the <li> elements to provide a better answer. It's uncommon to use the TaskQueue directly, often things can be refactored into custom elements or attributes that participate in the composition lifecycle more naturally. For example, if you need to do some jQuery stuff with your <li> elements, the "aurelia way" would be to separate this logic from your view-model using a custom attribute:

do-something.js

import {inject, customAttribute} from 'aurelia-framework';
import $ from 'jquery';

@customAttribute('do-something')
@inject(Element)
export class DoSomethingCustomAttribute {
  constructor(element) {
    // "element" will be the DOM element rendered from the
    // template this attribute appears on, in this example an <li> element
    this.element = element;
  }    

  // now we don't need the task queue because attached is called when the 
  // <li> element is attached.
  attached() {
    // this.value is whatever the custom attribute is bound to, in this case
    // its a "thing" from your "things" array.
    $(this.element).doSomething(this.value);
  }
}

Here's the usage:

app.html

<template>
  <require from="./do-something"></require>

  <ul>
    <li repeat.for="thing of things" do-something.bind="thing"></li>
  </ul>
</template>
like image 177
Jeremy Danyow Avatar answered Apr 24 '23 22:04

Jeremy Danyow


Would like to add here another option for catching DOM changes that is pretty simple, works not only for aurelia and could be really useful when you have some dynamic DOM changes triggered on user interaction, you can use MutationObserver https://developer.mozilla.org/en/docs/Web/API/MutationObserver

import {DOM} from 'aurelia-pal';
...
let mutationObserver = DOM.createMutationObserver(() => {
   // handle dom changes here
});

//start to observe, note you can set different options
mutationObserver.observe(someDomElement, {childList: true, subtree: true, characterData: true});

when you don't neet to observe anymore, you do mutationObserver.disconnect(); usually from detached()

like image 29
valichek Avatar answered Apr 24 '23 20:04

valichek