<loading> component

I would like to create a loading component to indicate a “loading…” before a specific property is loaded, see:

https://gist.dumber.app/?gist=93b92f2606e7172629868376aa5526a2

This gist is fully working, but, I would like to toggle the slot content with if.bind instead of show.bind, unfortunately this does not work (It’s by design, I know, it’s in the documentation).

I would like to prevent the evaluation and rendering of the view inside <loading> (my-component in my example) before the loading is “completed”, that’s why I would like to use if.bind.

Do you have another idea how to write such a reusable loading component?

Why not just use

<div class='container'>
<loading if.bind='!ready'></loading>
<my-component if.bind='ready'></my-component>
</div>

…and skip using slots? You can css it
.container > * { }

Kremnari

I could, but it’s less fancy! :slight_smile:

Today, our existing code is similar to your suggested solution:

<div if.bind="isReady">actual content</div>
<div else>Loading...</div>

This pattern exists in about 200 components in our application. Now, we would like to have:

  1. a fancier loading animation
  2. as little code as possible, as it affects 200 components

I tried it with slots, because <loading wait-for.bind="isReady">actual content</loading> is simpler and fancier in my opinion then everytime doing it with two if/else statements.

Actually, I have a thought, but I will need a day or two to play around with it…
I’m wondering if we can reverse it, set the “loading” indicator to be a custom attribute that overwrites and restores the element. The basically becomes virtual within the code of the customAttribute

I’ve had some success…

var loadingElem

export class LoadingAnimCustomAttribute {
  static inject = [Element]
  static setLoadingElem = (str) => {
    loadingElem = document.querySelector(str).cloneNode(true)
  }
  constructor(element) {
    this.element = element
    this.inner = element.cloneNode(true)
  }
  valueChanged(newVal) {
    if(newVal) {
      //This appears to work in the debugger
      this.element.replaceWith(this.inner)
      //But by function exit the elment is unchanged
    }
    if(!newVal) {
      this.element.replaceWith(loadingElem)
    }
  }
}

But there’s a failure noted in the code comments. I’m not sure why it is doing that…

Thanks a lot for your help! Just seen the new promise template controller in Aurelia 2, seems like this could help me in future!

Oh dam*! Yay! That will solve a use-case for me as well.

I haven’t been able to solve my solution yet. Are you ok with not having this solution for the time being?

The Promise controller would definitely be the way to do this in Aurelia 2. If you are using Aurelia 1, I am of the opinion there is no wrong solution if it works for you. Furthermore, you could also use the new <au-slot> functionality in Aurelia 2 to also allow dynamically slotted content with template controllers (if.bind and so on).

I wonder if you could leverage a custom binding behavior to sort this issue? It would be interesting to try or a custom attribute perhaps might be better suited? If I get a moment soon, I might see what I can come up with.

Thanks for your help! I finally found a solution:

(it’s a bit a hack, but later with aurelia 2 I can swap this (ugly) code with a clean usage of “au-slot” with “if.bind”)

component html

<template>
    <div if.bind="!isReady">${'Fancy loading animation...'.$translate()}</div>
    <slot></slot>
</template>

component typescript

import { autoinject, ViewCompiler, ViewResources } from 'aurelia-framework';
import { bindable, processContent } from 'aurelia-templating';

@autoinject
@processContent(Loading.processContent)
export class Loading
{
    @bindable()
    isReady: boolean;

    static processContent(compiler: ViewCompiler, resources: ViewResources, node: Element)
    {
        // Wrap View
        const wrapper = document.createElement('div');
        for (let child of Array.from(node.children))
        {
            wrapper.appendChild(child);
        }
        node.appendChild(wrapper);

        // Hide view
        const condition = node.getAttribute('is-ready.bind');
        wrapper.setAttribute('if.bind', condition);
        
        return true;
    }
}

usage

<loading is-ready.bind="dataLoaded">
    any content
</loading>