'Create a "ready" State (=mounted + dependent lib loaded) in a Svelte Component

As a first step, my test App is comprised of 2 files:

  • Comp who exposes a doSomething() function
  • App who need to trigger doSomething() in Comp as soon as possible (at the start of the app without any action from the user for example).

From what I know, this could be done like below (critiques are welcome here: pattern, coupling, effectiveness etc.)

part 1 (a bit of context)

App.svelte

<script>
import Comp from './Comp.svelte';
let comp;

function handleChildCompMounted(){
   comp.doSomething();
}
</script>

<Comp bind:this=comp on:mounted={handleCompMounted}/>

Comp.svelte

<script>
import {onMount, createEventDispatcher} from 'svelte';

const dispatch = createEventDispatcher();
onMount(()=>dispatch('mounted'));

export function doSomething(){ ... }
</script>

...

part 2 (the real problem)

Now problem is Comp uses an external library that needs to be loaded before being able to do anything. In other words, for App to call Comp.doSomething(), he must ensure that Comp is fully operational, that is to say that:

  1. Comp is mounted
  2. The Library is loaded for Comp to be able to use it in Comp.doSomething()

In order to do that, I'll introduce a LibraryLoader Class which will be in charge of loading the library (without loading it N times, manage timeout etc.) for all the potential other components of the application that would need this library to be loaded to be operational. Feel free to comment the below code cause I think it's the 1st or one of my first classes in javascript to be honest :D

const FAKE_LOAD_TIME = 3000;
const TIMEOUT = 5000;

export class LibraryLoader{
    
    #library_state = 'NOT_LOADED'; // NOT_LOADED || LOADING || LOADED
    #totalWaitTime = 0;
    
    isLoaded(){
        return this.#library_state === 'LOADED';
    }
    
    async getLibrary(){
        console.log('** getLibrary() called **');
        if(this.#library_state === 'LOADING'){
            while (this.#library_state !== 'LOADED') {
                console.log('** waiting 1sec ... **');
                await this.#wait(1000);
                this.#totalWaitTime += 1000;
                if (this.#totalWaitTime > TIMEOUT) {
                    console.log('Timeout!!! Library failed to load');
                    library_state = 'FAILED';
                    break;
                }
            }
        }

        if (this.#library_state === 'NOT_LOADED') { 
            this.#library_state = 'LOADING';
            console.log('** Loading Library **');
            await LibraryLoader.#loadLibrary();
            this.#library_state = 'LOADED'; 
            console.log('** Library is Loaded** ');
            dispatchEvent(new Event('loaded'));
        }
    }

    static async #loadLibrary(){
        window.myLib = null;
        await LibraryLoader.#wait(FAKE_LOAD_TIME);
        window.myLib = {name:"MY_LIBRARY"};
    }

    static #wait(ms) {
        return new Promise(function (resolve) {
            setTimeout(resolve, ms);
        });
    }
}

Now Comp tries to load the library using the LibraryLoader class ASAP and ... here you can see 2 things:

  1. My reactive statement used to check if the library is loaded doesn't work. (I don't know how to create and dispatch an event in the context of this LibraryLoader like we would do for a DOM Event)

  2. I didn't find a way to write a dispatch('ready') in a relevant place in the code.

<script>
import {onMount} from 'svelte';
import {LibraryLoader} from './libraryLoader.js';

console.log('Comp is asking for the Library');
const libLoader = new LibraryLoader(); 
libLoader.getLibrary();
    
$: { // REACTIVITY NOT WORKING HERE
        libLoader.isLoaded()
            ? console.log('library available for Comp')
            : console.log('library NOT available for Comp');
}
    
onMount(() => {
    console.log('Comp is mounted');
});
</script>

The last thing I need now is to make the Comp dispatch a 'ready' event when it is mounted and when the library he depends on is loaded too. This 'ready' event would be a trigger for other components which would need to call a Comp function that Comp is ready for that.

I'm sharing a REPL code, it will be more convenient for you guys:

https://svelte.dev/repl/cca38d8a3ac04cf4a356cc4ed5e336d1?version=3.46.2

Once again, many thanks for your help :)

Edit 24/01/2022

For those interested, I've introduced a Dispatcher class to put aside the problem of dispatching an event not attached to a DOM elem. So, in the following REPL, you'll see the problem underlined by @Bob Fanger which is the complexity and time to try to synchronise states between components.

https://svelte.dev/repl/d3d48eb4106c4b24b984c4b70fd32271?version=3.46.2

Using slots could be interesting to use another component conditionally to the availability of a specific one. In the below REPL, we do 2 things:

  • Comp finally trigger the doSomething himself instead of delegating this responsibility to App. This decouple the code a bit.

  • App displays uses Comp slot to display a message or use another component which would be dependent on the readiness of Comp.

https://svelte.dev/repl/ef7876d4fa8847d1ad1e13eb616b95fa?version=3.46.2



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source