Alpine: Delayed component initialisation

Created on 9 Apr 2020  路  9Comments  路  Source: alpinejs/alpine

Currently Alpine will instantly initialise any component that has the x-data attribute. One way to circumvent this is by using undocumented window.deferLoadingAlpine callback but this will defer all components.

Another way would be using a nested component, wrapped inside of an x-if like so:

<div x-data="{ show: false }">
    <template x-if="show">
        <div x-data="{}">
            // only initialised when show is true
        </div>
    </template>
</div>

My proposal is a new Alpine.initialise() or similar method that can be used to manually initialise a component. It would receive a target element, for example a <div x-data="{}"> and it would then initialise and setup that component.

This would allow you to lazily "load" or observe a component until you need it, reducing the number of event listeners registered across your application / site.

Alongside this, there would need to be a new modifier or directive such as x-data.lazy or x-lazy which would be used to mark the component as "lazy". When Alpine then boots up, we can check to see if that element has said attribute or modifier and skip initialisation. Later on, the Alpine.initialise(target) method can be used to start observations and run any pre-op setup Alpine does.

The Alpine.initialise(target) function could also take a second data object parameter which acts as the data for the component. This would allow you to initialise a component using data from somewhere else in your application, not necessarily a parent component.

Just a suggestion, no plans to make a PR yet. Wanted to get some opinions and thoughts on it first. If people like this idea and would find it useful, I can make a PR to add support.

/cc @calebporzio @SimoTod @HugoDF

Most helpful comment

I think this would be useful, especially when some components have been extracted to a separate js file, and not others. The non-extracted components could initialize right away, while the others could be deferred and initialized as needed. Wrapping inside an x-if condition means the output is hidden initially which is not great if you have to wait for the extracted component js to load.

All 9 comments

I think something like this would be better targeted for v3 which might change the scope a bit too.

I'll leave the issue open for discussion now, any and all thoughts are welcome!

I don't have any strong feelings against it. It's a different direction from the initial scope of the project where all the behaviours should be described rather then called in an imperative way but I do see the improvements that it would bring.
Let's see what Caleb thinks.

I don't have strong feeling against it. It's a different direction from the initial scope of the project where all the behaviours should be described rather then called in an imperative way but I do see the improvements that it would bring.

Let's see what Caleb thinks.

Yeah for sure. It's definitely something that should probably target v3 alongside some of the newer focuses on utilities on the Alpine object.

As noted above, there are ways to achieve a similar effect with the current API using x-if directives, but I feel as if the API should be simpler for something as small as this. Only small if you don't include passing data to the initialise the component with.

Thanks for the thoughts tho Simone, appreciate it!

I think this would be useful, especially when some components have been extracted to a separate js file, and not others. The non-extracted components could initialize right away, while the others could be deferred and initialized as needed. Wrapping inside an x-if condition means the output is hidden initially which is not great if you have to wait for the extracted component js to load.

I think this would be useful, especially when some components have been extracted to a separate js file, and not others. The non-extracted components could initialize right away, while the others could be deferred and initialized as needed. Wrapping inside an x-if condition means the output is hidden initially which is not great if you have to wait for the extracted component js to load.

Yeah 100%. Let's see if Caleb has any thoughts before I dive into more implementation details. Thanks for the thoughts though!

I was playing with some ideas and came up with this approach https://codepen.io/jreviews/pen/XWmXqqJ

<div x-data='{foo: "bar"}' x-init="console.log(1)">
  <h1 class="font-bold">Instant</h1>
  <span x-text="foo"></span>
</div> 

<div defer-x-data='load()' x-init="console.log(2)">
  <h1 class="font-bold">Deferred</h1>
  <span x-text="bar"></span>
</div> 
function load() {
  return {
    "bar": "foo"
  }
}  

window.addEventListener('DOMContentLoaded', () => {
  const comp = document.querySelector("[defer-x-data]")
  comp.setAttribute('x-data', comp.getAttribute('defer-x-data'))
    Alpine.start()
    // document.dispatchEvent(new CustomEvent('turbolinks:load'))
})

Do you see any issues with the approach?

Alpine already starts after the DOM is ready, so you'd be starting Alpine twice. Alpine will only initialise a component if it's __x property is falsy, so there's not _loads_ of overhead but there would still be quite a bit.

I'm thinking more about a single method on the winodw.Alpine object that will initialise a single component. I'm not sure using defer-x-data would be too great either.

I'd go for this approach instead:

<div x-data.defer="{ foo: 'bar' }" id="component">
    <h1 x-text="foo"></h1>
</div>

Now there's no need to manually alter the element's attributes. Instead, we can just take the value of the the x-data.defer attribute and use it directly.

When Alpine starts, it would ignore any attributes that have the defer modifier. Using modifiers for this makes more sense since it's already a feature for other directives.

The Alpine.load() or Alpine.observe() method would then setup this single component, without having to scan the entire DOM again since the element would be passed through as the first parameter Alpine.load(document.getElementById('component')) for example.

Good thoughts tho!

ok, ok. Was just thinking of a way to do it now, while the feature is unavailable. Instead of doing Alpine.start(), this also works, _for now_ to initialize individual components:

const comp = document.querySelector("[defer-x-data]")
comp.setAttribute('x-data', comp.getAttribute('defer-x-data'))
Alpine.initializeComponent(comp);

Yeah, there are ways of doing this at the moment for sure. I mentioned a couple in the original comment too. Wasn't crapping on your work by the way, your method does indeed work, as well as the way you mentioned above.

I'm thinking more about tightening up the API and making it easier to do these sort of niche-r things. Thanks for your input again!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aolko picture aolko  路  5Comments

adinata-id picture adinata-id  路  4Comments

zaydek picture zaydek  路  3Comments

mrmathewc picture mrmathewc  路  4Comments

dkuku picture dkuku  路  5Comments