Skip to content

PreLoading Lovelace Elements

Thomas Lovén edited this page Mar 4, 2022 · 8 revisions

This article is aimed at developers of custom lovelace cards


Home Assistant and Lovelace makes use of a lot of custom built webcomponents.

You may want to use the same ones in your custom cards to provide a consistent user experience. Often this is very easy to do. Just add e.g.

<ha-entity-toggle></ha-entity-toggle>

to your card and you will be able to use the ha-entity-toggle element. Feed it with the hass object and an stateObj and it will automatically track the state of your entities and display a toggle or the lightning bolt thingies when appropriate.

But sometimes this may just not work. Some elements may only work if your custom card is placed in a view with other cards, or if you first go to a view with a different card and then back to the one with yours. What's going on?

Lazy loading.
The Home Assistant frontend is HUUUUUUGE, but most users don't need all of it. At least not at once. That's why it's cut up into bite-sized chunks that are sent from the server to your browser as they are needed. This is called lazy-loading and really helps with performance, but can be a bit annoying for custom card developers.

The chunks all have user friendly names like chunk.e018441031620d9eacc0.js and because of heavy code compression and automagic chunkification for optimal performance, there's pretty much no way to know what's in which chunk. But often you can trick the Home Assistant frontend to load the one you want for you automatically.
All you need is to know where the element you want is used.

Elements in Cards

Say we want to use the ha-gauge element.

To test if that's available we can open up a Home Assistant frontend, make a blank lovelace view and then open up the browser console.

In there we'll type:

> customElements.get("ha-gauge")
<- undefined

Not very encouraging compared to e.g.

> customElements.get("ha-card")
<- class extends t{constructor(...t){super(...t),e(this)}}

ha-card is so often used that it's loaded by default even if there are no cards in the current view.

So we need to find something that uses ha-gauge.
This one's easy to guess, but if you can't you can either search for it in the Home Assistant frontend repo (as I'm writing this, the search function is utterly broken though) or by cloning the repository and running git grep "ha-gauge".
We quickly find that ha-gauge is imported by the gauge card (no surprises there) https://github.com/home-assistant/frontend/blob/dev/src/panels/lovelace/cards/hui-gauge-card.ts#L17

So what we need to do in our custom card is to - at some point - trick lovelace into downloading the gauge card. And that's really easy with the card helpers.

const cardHelpers = await window.loadCardHelpers(); // This is a built-in function in lovelace
cardHelpers.createCardElement({type: "gauge"}); // You can ignore what's returned here
                                                // The configuration passed to createCardElement doesn't even need to be valid,
                                                // though an invalid configuration will generate an error in the console.

And that's it. We can even run this in the console to verify it works:

> customElements.get("ha-gauge")
<- undefined
> cardHelpers = await window.loadCardHelpers()
<- Module {}
> cardHelpers.createCardElement({type: "gauge"})
<- <hui-gauge-card></hui-gauge-card>> customElements.get("ha-gauge")
<- class r extends t{constructor(...t){super(...t),e(this)}}

So all we need to do is make sure the two lines above are run at some point. It could be good to put them in e.g. the constructor or setConfig of your custom card. It could also be nice to add a guard clause to only run them if necessary:

if (!customElements.get("ha-gauge")) {
  const cardHelpers = await window.loadCardHelpers();
  cardHelpers.createCardElement({type: "gauge"});
}

Elements used elsewhere

This same approach will work for any element used in a card, badge, picture-elements-element, entities card row, card header or card footer. There's even a function in the customCardHelpers to load more-info dialogs so we can use the elements in those which exists exclusively for this purpose.

Check them out: https://github.com/home-assistant/frontend/blob/dev/src/panels/lovelace/custom-card-helpers.ts

Editor elements

We can also access elements used in card editors, but that's a little bit more tricky.

Say we want an ha-entity-picker element. That's used in the entities card editor, so we need to load that one. Luckily, all cards with an editor expose a function that loads their editor:

// First we get an entities card element
const cardHelpers = await window.loadCardHelpers();
const entitiesCard = await cardHelpers.createCardElement({type: "entities", entities: []}); // A valid config avoids errors

// Then we make it load its editor through the static getConfigElement method
entitiesCard.constructor.getConfigElement();

After this, the element should be accessible.

A note on stability

This may seem "hacky". Creating elements that aren't used. Tricking the interface into doing things... won't it break in the next Home Assistant update?

The answer is that it may. However, there's been many long discussions on the best way to make elements available to custom card developers, and the consensus among the core development team and a number of custom card developers is that this is in fact the best way.

window.loadCardHelpers is a published interface that is unlikely to change without notice, and as mentioned above the cardHelpers.importMoreInfoControl function even exists exclusively for this purpose (the rest are used internally by Home Assistant as well).

Custom cards were not meant to use the same elements as the core cards, but it has turned out to be very popular to do so. And with good reason - the elements are great ones and everyone gains from a consistent user experience.

In short: the functionality described here is likely here to stay.

ScopedRegistry

If your card is using the ScopedRegistryHost mixin you won't have access to any of Home Assistants custom elements without a bit of extra work.

See this gist for an example.