Parent & Subcomponent communication with delay

Say I have a custom component that loads up a <canvas> tag, some graphical features that it draws. Then the parent component must add more information to it later on through fetch-client and thus update the drawing.

However, the object class in the subcomponent is not ready at the very start. It needs time to load.

export class ParentSinglePage {
  @bindable childClass;

  loadEvenMoreData(){
      console.log("Object class is NOT ready... :( .. I can't believe you tried to load data into a 'null' object", this.childClass);
   }
  childClassChanged(nv, ov){
      console.log("Object class seems ready: ", this.childClass);
      console.log("Exactly same is nv: ", nv);
      console.log("Okay, so why even bother with .call binding... just call loadData() from here?");
  }
}
<childElement child-class="child-class" load-even-more-data.call="readyToLoad()"></childElement>

Can I do this without using events ?

The child component is calling “ReadyToLoad” AFTER it is ready, but I guess, it perhaps should be a promise? Or is there a way to promise bindables or something?

Or should I rather call the “loadData()” in the Changed handler? The problem with this is if I have “50 childElements” on the page, then I have to create 50 change-handlers… :cold_sweat:

I’d rather have a way for each subcomponent (child element) to call its own “loadDataSpecific()” function in the PARENT. But only after subcomponent (child element) has readied the class object.

When the component is ready call dispatchEvent on it with a bubbling event. Then in the parent, bind a delegate on a container with all the children. This single event handler will catch all the ready events. The event target will give you the element in question.

Are you suggesting something like this gist?

You add a selected.delegate=MySpecificExtraDataLoadFunction() to your subelement tag in the parent html and have the “valueChanged” function inside the child custom element class and it bubbles the event when childClass changes and thus has new data inside (as in it has been loaded!)?

It is sort of what I wanted to do, abstract out the inner-workings of the element into the child element ES6 script, and just start each parent with a number of functions or a big-handler that can load extra data into specific child elements.

<myelement s.delegate='loadDataY()'></>
<myelement s.delegate='loadDataX()'></>
<myelement s.delegate='loadDataZ()'></>

And only have in parent class:

loadDataY() { // load everything here, access this.InstanceOfThisChildYDataObject }
loadDataX() { // load everything here, access this.InstanceOfThisChildXDataObject }
loadDataZ() { // load everything here, access this.InstanceOfThisChildZDataObject }

Or would it be better to just have a MessageType EventAggregator??

Almost like this with a little change

<div s.delegate='loadData($event)'>
// Here go children
<div/>
1 Like

Ok I just did my experiments with $event way, and I don’t think this method will work because at the end of this, I have to still set this.childClass = $event.detail (circular copying into a bindable). This bubbling up works well for transferring data from the child to the parent. But I need both-directions. Two-way binding.

The child starts the canvas, the parent adds more data into it after child is ready and the child needs to also see the parent’s changes to the data of the canvas to redraw things. There may be other buttons and events that later on may interact with that object.

I almost need like a singleton class of sorts that is accessibly by both parent and child component. The parent also needs to know when the canvas is ready before adding more data.

Why not do it via bindables on a child? The parent passes the data it wants to draw and the child decides when it is ready and draws

I would use the data to drive custom elements inside a canvas custom element that use the canvas API to render themselves. With some clever design, you’ll be able to simplify using the canvas.

<my-canvas>
  <my-circle repeat.for="circle of loadedCircles" 
             r.bind="circle.r" x.bind="circle.x" y.bind="circle.y">
  </my-circle>
</my-canvas>

This solves your async loading issue because you (1) load the data and then (2) use the data to drive components in the view which (3) cause lifecycle events to be called.

Canvas is tricky because there is no “path” object like there is in svg. You have to wipe the canvas clean and rerender. That means you have to do a little bit of extra heavy lifting to communicate to the child elements when the parent is wiped clean. There’s also a timing issue between Aurelia’s component render lifecycle and the canvas drawing.

Here’s an demo: https://gist.run/?id=cc632f6e8960c9c212d1e4ce08ef5749

I recommend using DOM events only for bubbling actual events in a hierarchy, only when the goal is to inform other components of an event. For example, you might bubble an “dataloaded” event to say “hey, the data loaded”, so another component can say “when the data loads, hide the loader.” In this case, you’re doing the opposite, you’re trying to say “when the data loads, the canvas should do something!” That’s not an event, so I wouldn’t fire a DOM event.

You could handle this with the event aggregator, but I don’t recommend it. The rule of thumb is don’t use the event aggregator unless you’re building plugins. If you do use the event aggregator, again, it should be to say “something happened!” and not “I want something to happen,” and it should only be in the case where a bubbled DOM event cannot work.

If this answer was helpful and you’d like to see more like them, please consider supporting me on Patreon: https://www.patreon.com/davismj

Edit My example disappeared. Let me try to rebuild it.
Update Fixed, and improved