Bindable element

In intent-driven development, the emphasis on modularity is paramount. Similarly, in web development, the reusability of functionality is sought after, facilitating code written once to be utilized in multiple instances throughout the application.

Typically in web development, this could be attained by creating a custom element derived from HTMLElement. However, a hurdle arises when engaging with binding engines, as it necessitates the definition of a custom context and the parsing of markup for the binding engine to function effectively. This encompasses any binding expression, ranging from UI interactions to data display.

The BindableElement class furnishes the capacity to delineate a custom element while concurrently being binding-aware. It autonomously generates its binding context and parses its markup, alleviating you from these tasks. Moreover, it manages memory cleanup during the disconnected callback, obviating the need to manually remove events. As long as the binding engine features are employed for its creation, the component autonomously oversees this cleanup, simplifying the process on your end.

It’s crucial to mention that when you override the connectedCallback or disconnectedCallback, you must also invoke the base implementation. Failing to do so can lead to unexpected behavior. Specifically, if you overlook the connectedCallback, loading operations might not work properly, so it’s recommended to use the load method instead. Neglecting to call the base of disconnectedCallback can result in memory leaks.

export class MyComponent extends crs.classes.BindableElement {
    get shadowDom() {
        return true;

    get html() {
        return import.meta.url.replace(".js", ".html");
    get mobi() {
        return import.meta.url.replace(".js", ".mobi.html");
    async connectedCallback() {
        await super.connectedCallback();
        // ... do your stuff
    async preLoad() {
        // ... load data before parsing is done
    async load() {
        requestAnimationFrame(async () => {
            // ... do your stuff
    async disconnectedCallback() {
        // ... do your clean up.
        await super.disconnectedCallback();

customElements.define("my-component", MyComponent);

The standard practice while working with web components entails having three distinct files, each dedicated to a different type: JavaScript, HTML, and CSS. Although the file names are identical, their extensions differ to indicate the type. The naming convention dictates that the file names should match the custom element name, and all these files should be housed in a folder bearing the same name as the custom element.

my-component (folder)

It’s possible to have a component devoid of HTML, but in such cases, you need to return null for the html property. Refer to the details at the bottom for more information.

The BindableElement also employs the utility function disposeProperties during disconnection. This action constitutes an aggressive cleanup, where properties are meticulously inspected and nullified recursively on the component. This thorough cleanup is designed to address any overlooked cleanup by the developer. Although this cleanup is executed on your behalf, it’s still advisable to manually tidy up your elements prior to this aggressive cleanup, ensuring any special cases are adequately handled.

bidread onlynumberThe context id for this element
htmlread onlystringWhat is the file location where the markup should be loaded from.
hasStyleread onlyboolDoes this element have it’s own stylesheet.
Stylesheets are loaded as part of the HTML using a link tag.
allowNotificationsread writeboolTrue if event dispatching is allowed.
False if dispatching is suppressed
shadowDomread onlyboolTrue if the component must be created as a shadowdom component with a shadow root.
mobiread onlystringIn scenarios where a specialized UI is desired for mobile devices, specify the HTML path here, similar to how it might be done with standard HTML. If the application identifies a mobile device, it will load this specified HTML instead of the main HTML.
Bindable Element Properties

Several methods are noteworthy as they are frequently utilized in bindable elements.

getPropertypropertyRetrieve the value of a property from the current binding context.
Assign the given value to the property on the current context.
Similar to setProperty, but in this scenario, a callback is passed. The callback must be asynchronous. Upon activation, the callback provides the current value of that property as a parameter. You can then return the desired value, and the property on the context will be updated accordingly.
notifyevent name
This method encapsulates the dispatchEvent method, streamlining the event dispatch process and ensuring the correct options are utilized during the dispatch.
Bindable Element Methods

In addition to the above methods, the events mix in is also added.


This introduces two more methods:

  1. registerEvent
  2. unregisterEvent

The registerEvent method tracks the events you’ve added, eliminating the need to call unregisterEvent, as these events will be automatically cleaned up upon the element’s disconnection from the DOM.

On the other hand, unregisterEvent is beneficial for temporary events that are no longer needed and should be removed accordingly.

Behind the scenes, registerEvent and unregisterEvent leverage the standard addEventListener and removeEventListener methods on elements, with the primary aim being tracking and automatic cleanup. As previously mentioned, when employing the binding engine to monitor events, particularly through the use of registerEvent, there’s no need to be concerned about memory leaks from events that were overlooked and not cleaned up.

registerEvent takes four parameters

elementThe HTMLElement on which the event needs to be registered
eventThe event you are monitoring
callbackThe asynchronous function to invoke upon the event’s trigger
eventOptionsAdditional event listener options you wish to include. This is an optional parameter, defaulting to null.
regisgteEvent parameters

unregisterEvent utilizes the first three parameters mentioned above.

this.registerEvent(buttonElement, "click", this.#click);
this.unregisterEvent(buttonElement, "click", this.#click);

There are two distinct timing methods:

  1. preLoad
  2. load

These can be overridden in your instance to accommodate timing-sensitive operations.

The preLoad method is invoked prior to parsing, allowing for the loading of data necessary for one-time bindings within the asynchronous preLoad method.

On the other hand, the load method is triggered as the final step of initialization, post the loading of all components.