I have a LitElement-based web component to assist with advance forms validation. I would like to allow extensible validators to be added later via dependency injection.
In my testing the following code, IValidate classes registered before my web component is defined are available to the web component, but classes registered after the web component is defined are not:
import { html, css, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { container, injectable, InjectionToken } from 'tsyringe';
export interface IValidate {
validate(input: HTMLElement): boolean;
}
export const ValidateToken: InjectionToken<IValidate> = 'ValidateToken';
@injectable()
export class ZipCodeValidator implements IValidate {
validate(input: HTMLElement): boolean { return true; /* Implementation */ };
}
container.register(ValidateToken, { useClass: ZipCodeValidator });
@customElement('my-validate')
export class MyValidate extends LitElement {
validators: IValidate[] = [];
async connectedCallback() {
super.connectedCallback();
this.validators = container.resolveAll<IValidate>(ValidateToken);
debugger; // this is hit first; this.validators.length == 1; I expected 2
}
}
@injectable()
export class ExistsValidator implements IValidate {
validate(input: HTMLElement): boolean { return true; /* Implementation */ };
}
debugger; // this is hit 2nd
container.register(ValidateToken, { useClass: ExistsValidator });
When I debug:
- The debugger breakpoint in
connectedCallback()is hit before the 2nd debugger - The ExistsValidator has not yet been registered.
I am surprised that the connectedCallback is being executed before we ever hit the container.register(ValidateToken, { useClass: ExistsValidator });
I create a distinct test that works as I would expect:
import 'reflect-metadata';
import { container, injectable, InjectionToken } from 'tsyringe';
import { expect } from '@esm-bundle/chai';
export interface IFoo {
validate(input: string): boolean;
}
export const FooToken: InjectionToken<IFoo> = 'FooToken';
@injectable()
export class FooA implements IFoo {
validate(input: string): boolean { return input === "a" };
}
container.register(FooToken, { useClass: FooA });
@injectable()
export class FooB implements IFoo {
validate(input: string): boolean { return input === "b" };
}
container.register(FooToken, { useClass: FooB });
describe('DI', () => {
it('registers all validators', () => {
const validators = container.resolveAll<IFoo>(FooToken);
expect(validators.length).to.equal(4);
console.log(validators.length);
});
})
@injectable()
export class FooC implements IFoo {
validate(input: string): boolean { return input === "c" };
}
container.register(FooToken, { useClass: FooC });
@injectable()
export class FooD implements IFoo {
validate(input: string): boolean { return input === "d" };
}
container.register(FooToken, { useClass: FooD });
What am I missing? Is there a 'proper' way to mix DI (tsyrige) with Lit?