All of the answers really helped me understand the issue, thank you!
At my organization, we have developed a component library that has been around for a few years, and unfortunately the various interacting projects can't always be on the same versions. So let's say that the WebHeader team uses an old version of the company's <corp-input>
component, but some page that has to include the WebHeader needs to use a newer version of the same component?
Our devs have long since implemented a try-catch around the Lit components' customElements.define
calls, but this means that the page is stuck with the old version unwil the WebHeader team gets to the upgrade in their backlog.
Well, we engineered a solution: If the team knows that there will be a conflict, instead of using <corp-input>
, what if instead they used <corp-input-new>
, and told the Lit component to render those tags? This way, the header looks good despite older component versions being loaded AND the page can benefit from the latest version alongside the older ones.
Here's how you do it:
If you try to just call customElements.define('corp-input-new', CorpInput)
, you'll get a Javascript exception saying that a component can only defined to render one tag name. BUT all you need is a copy of that Component type, which you can get for free by using inheritance! So, first you say class CorpInputNew extends CorpInput { };
, and then you say customElements.define('corp-input-new', CorpInputNew);
. You'll probably need to do this in your Angular's app.component.ts constructor.
There may be a method inside of your library's Component that is hard-coded to do something undesirable. For instance, I needed to extend CorpSelect, which also means that I needed to extend CorpSelectOption and all the functions that explicitly query on this.querySelectorAll('corp-select-option')
.
i. If the method is not read-only, you can just re-implement the method inside of your extended class's curly braces: ... extends CorpInput { update(changedProps: any): { ... } }
.
ii. You probably won't have access to the this
keyword (at least, not as it's used in the original Component to select class attributes), so instead try to select on yourself and assign it to something: const thisInput: any = this.querySelectorAll('corp-input-new'); thisInput.someNonPrivateFunction(...);
.
iii. Sometimes there are read-only methods. In Lit, the component may have something like static get selectorItem(): string { return 'corp-select-option,corp-select-group'; }
, which cannot be easily overridden like above. Instead, you'll need to do this after your extended class's definition: Object.defineProperties(CorpSelectNew, { selectorItem: { value: 'corp-select-option,corp-select-group', writable: true } });
.
Lastly, you could implement a versioning system on your components to more easily identify exactly which version is being used when debugging, since minified component Javascript is a nightmare to read. In your custom component, when you register it, make sure you have a public static attribute like "VERSION". That way, you can always ask your browser which version is registered by writing in the console: customElements.get('corp-input').VERSION
.