Override a Custom Element

I’ve been researching how to do the following, but haven’t seen a direct solution:

The challenges are two fold:

  1. Be able to specify which custom element to use at runtime. In other words, custom elements are used throughout my app, but I’d like to specify whether to use implementation A or B of that custom element.
    Example:
    The component is used here:
<template>
  <hello-world></hello-world>
</template>

And I have two (or more) possible implementations of <hello-world> custom element.
A)

export class HelloWorld {
  message: string = "Hello World!"
}

//HTML
<template>
<h1>${message}</h1>
</template>

B)

export class HelloWorld {
  message: string = "Goodbye World!"
}

//HTML
<template>
<h1>My message: ${message}</h1>
</template>
  1. To make things more complicated, the “code” for the custom element is not part of the codebase of my app but loaded dynamically from an external resource. Is this even possible to achieve? Can a class/implementation (that happens to be valid “aurelia code”) be loaded from outside of aurelia and set as a custom element? I know there’s a way to register global resources, but I would think those resources have to exist/be defined already within the app.
    I’m using webpack, so I fear this is even more difficult, if not impossible to do.

Any direction or advice would be much appreciated.
Thank you.

1 Like

Assuming you want to override the markup for a given view-model class registering a inline view strategy for the class probably will do the job.

import {metadata} from 'aurelia-metadata';
import {ViewLocator} from 'aurelia-templating';

const strategy = new InlineViewStrategy(markup, undefined, undefined); // <-- `markup` contains the HTML string
metadata.define(ViewLocator.viewStrategyMetadataKey, strategy, HelloWorld);

I think defining the metadata in that way in the bootstrapping phase might work. Note that this code is mostly hypothetical, and not tested. Refer the @inlineView source for more details.

If the backing view model class also changes, and you still need a constant CE name, you might need to define the metadata for that as well. See below.

I have assumed that you are not yet using Aurelia2. IMO these sort of things will be very easy in Aurelia2.

Would you be able to share the reason why you want to do this BTW?

1 Like

The compose element might be another way to solve your issue by allowing to specify a dynamic view and viewmodel. If the backing Code is no full view, create a viewmodel that will act as wrapper and inside its activate Hook load your data and return a promise once done. Take a look at the docs about compose to get a few ideas

2 Likes

Thanks for your help!
Yes, I would need to override both the markup and view model. I’ll take a look at what you suggested.
As far as Aurelia2 - I’m currently using Aurelia 1, but would be open to switching to Aurelia 2. Do you know what I should look at there to achieve the same result?

In terms of a use case, imagine the following:
I have a platform with core components used throughout the app. A client or third party would like to customize this app and provide an alternative implementation of any of the components used in the app. I don’t want their code living inside my codebase and would prefer to load their implementations of the components separately.
Do you have a better way to achieve this?

Thanks for the suggestion. I’d prefer not to use “compose” because then I essentially would have to use compose elements everywhere instead of custom elements. Other than a lack of clarity/organization, I think that would pose some additional challenges as well as custom elements are implemented a bit differently than compose elements, as far as I understand.

You have an interesting use case. I am assuming that you have a root level basic markup with fixed names for the custom elements.

For example, in you base markup you have

<foo-bar></foo-bar>
<fizz-buzz></fizz-buzz>

and then you want the client to provide the impl. for foo-bar and fizz-buzz. Is that correct?

If so then with Au2 achieving that is a breeze due existence of first-class APIs to do this kind of things. With Au2 it might look something like this.

container
  .register(
    CustomElement.define({name: 'foo-bar', template: fooBarTemplate}, FooBarImpl),
    CustomElement.define({name: 'fizz-buzz', template: fizzBuzzTemplate}, FizzBuzzImpl),
  )

Although the example shown is verbose it offers max flexibility. To know more about Aurelia2 refer the docs. Note that although it is pretty stable, it still is a work in progress.

On a second thought, there might be even easier solution for your use-case. If the client always owns the markup as well as backing viewmodel class, then there is no need for this gymnastic. Especially if you provide the “framework” part of it, and the client provides the “app” part of it. The details lies here

A client or third party would like to customize this app

The question is how the client will customize it exactly?

That’s great news about Aurelia2. I’ll have to look into that more.
Would registering the components work in the example you provided if I loaded the actual markup/viewmodel/class from an external resource, or do the references (like fooBarTemplate) have to alright exist in the compiled code?

The client doesn’t own the app part either - it’s a single instance of the platform/app. They would be able to provide an external script/file that is loaded into the app, that specifies and contains the components they would like to override, including the source code for said components.

Would it be possible for the client to offer an Aurelia Plugin instead which your app is using? Or is his code too dynamic or the approach to cumbersome?

1 Like

I’d consider the Aurelia Plugin approach, but can you elaborate on how that would work? Would the plugin make it easier to override existing custom elements? How are you thinking the plugin would work?

Well with the plugin approach they could provide the whole component at once or just parts like the ViewModel. Essentially they get a sub-app to go crazy and expose whatever is necessary and you include that during Plugin setup. The main benefit I see with that is that the client wouldnt be limited to solely that one custom element but could provide whatever necessary plus have a test-environment to check things outside your app.

As for the general question of one name but two elements. I would just ditch that. Let them namespace their elements like client-hello-world and you decide what to show original or client hello world. If you insist though, above scenario from @Sayan751 could still work

1 Like