Component lifecycle in relation with if.bind

Depending on if.bind or show.bind in root component, the lifecycle seems to be different in my component. Does someone have an explanation for that behaviour?

1 Like

if.bind will append/remove that dom based on condition.
show.bind will always render the dom the first time, never remove it from dom tree. It simply toggles a css class aurelia-hide on that element.

.aurelia-hide {
display: none !important;
}

You can clearly see from dev console that your initial rendering contains one <my-component> (instead of zero) with aurelia-hide in its class attribute.

1 Like

Thanks for your reply. I know this difference, but in my opinion this should not affect the behaviour of bind vs attached:

bind()
{
	this.value = 'item-1';
}

attached()
{
	this.divElement.style.backgroundColor = this.divElement.innerHTML == 'item-1'
		? '#0a0'
		: '#a00';
}

With show.bind, the bind process is done in attached cycle, with if.bind, the bind process is not yet done in attached cycle. Is this by design? Seems more like a bug to me :confused:

1 Like

Oh, sorry, I totally overlooked your code.

Bind is before attached, even for the element behind if bind, you can add console.log in both bind and attached to see that.

But it seems the rendering is not, for the element behind if bind, although bind is before attached, the innerHTML is not yet updated to reflect the model change.

So yes, there must be some rendering timing difference due to the if.bind. @bigopon might know some details.

Aurelia 1 attached did have some quirky timing, Aurelia 2 did clear that up. In Aurelia 1 attached, if you want to make sure dom is ready (especially when it contains some other custom elements), you can use setTimeout inside attached.

  attached()
  {
    console.log('attached');
    setTimeout(() => {
      this.divElement.style.backgroundColor = this.divElement.innerHTML == 'item-1'
        ? '#0a0'
        : '#a00';
    });
  }
1 Like

Thanks for your help. I used taskQueue.queueMicroTask so far for such situations, could you explain the difference between setTimeout() and queueMicroTask()?

Is setTimeout working reliable here?

1 Like

I use queueMicroTask before too, but moved to setTimout to avoid some DI code.
There is no much obvious difference. Yes setTimeout is reliable in this case.

In browser without mutation observer api, task queue uses setTimeout.

2 Likes

Thanks for your feedback. Please have a look at the gist again (I added some more binding work to test the reliability)

After 100-150 repeats it stops working… Looks like setTimeout() does not wait for the whole binding process…

1 Like

Interesting finding.

But it’s not a setTimeout issue, because it can be reproduced with queueMicroTask. It could be an edge case between repeater and if bind.

import { bindable, autoinject, TaskQueue } from 'aurelia-framework';

@autoinject
export class MyComponent {
  @bindable
  xy: string;
  
  value: string;
  divElement: HtmlDivElement;
  taskQueue: TaskQueue;
  
  constructor(taskQueue: TaskQueue) {
    this.taskQueue = taskQueue;    
  }

  bind()
  {
    this.value = 'item-' + this.xy;
  }
  
  attached()
  {
    this.taskQueue.queueMicroTask(() => {
      this.divElement.style.backgroundColor = this.divElement.innerHTML.startsWith('item')
        ? '#0a0'
        : '#a00';
    });
  }
}
1 Like

I updated the gist, seems like its not due to repeat.for…

It’s very irregular, sometimes it works, sometimes not

1 Like

Here’s an example https://gist.dumber.app/?gist=0def4fd948542fe32c574a7226bb0c11 with precise timing control & everything related to it. You can see there’ 2 parts:

  • .queueMicroTask() instead of .setTimeout()

  • disableConnectQueue()

  • .queueMicroTask() is used, is to handle timing, it needs 1 “tick” after attached to process the binding, as the binding update of ${value} is queued after .attached

  • disableConnectQueue() is used, to remove an optimization case where startup would queue so many tasks that could slow down free the app for a short period.


Timing of if + ${value}+ attached combo:

  1. user clicks on the button, .showComponent = true -> queue update all if bindings
  2. Tick 1: condition property of [if]s receive true-> queue change handler call (conditionChanged)
  3. Tick 2: conditionChanged called, each [if] creates its view (includes <my-component/> instance), then call .bind() synchronously, .attached() immediately (synchronously).
    3.1 Calling bind() -> set value to true -> queue binding update for text ${value}, so HTML is not rendered fully yet for <my-component/>, hence div.innerHTML === ''
  4. Tick 3: ${value} bindings update

You can see that step 3 is only 1 tick before step 4, hence we only need to do .queueMicroTask() in attached, instead of .setTimeout(). The .disableConnectQueue() is used to remove an optimization in https://github.com/aurelia/binding/blob/master/src/connect-queue.js for handling extreme cases.


  • If you control the source, then use the source data directly to manipulate, instead of waiting for the DOM to be updated, then get the outcome back to process your data.
  • If you have to do so, .setTimeout() is a ok way to deal with this, as there could be nested if/repeat etc… Having your code in in a macro task with setTimeout will guarantee it runs after all have settled.
  • in v2, we no longer employ a micro task queue to propagate binding changes, so you won’t get this issue in v2.
1 Like