fix(elements): do not break when the constructor of an Angular Element is not called (#36114)
Previously, the correct behavior of Angular custom elements relied on the constructor being called (and thus the `injector` property being initialized). However, some polyfills (e.g. `document-register-element`) do not call the constructor of custom elements, which resulted in the `injector` property being undefined and the `NgElementStrategy` failing to be instantiated. This commit fixes it by being tolerant to the `injector` property being undefined and falling back to the injector passed to the `createCustomElement()` config. NOTE: We don't have proper tests exercising the situation where the constructor is not called. For now this is tested using a Google internal test suite (which is how this issue was caught). This commit also adds a rudimentary unit test to emulate this situation. PR Close #36114
This commit is contained in:
parent
2fc5ae561b
commit
89b44d1900
|
@ -144,8 +144,9 @@ export function createCustomElement<P>(
|
|||
//
|
||||
// TODO(andrewseguin): Add e2e tests that cover cases where the constructor isn't called. For
|
||||
// now this is tested using a Google internal test suite.
|
||||
if (this._ngElementStrategy === null) {
|
||||
const strategy = this._ngElementStrategy = strategyFactory.create(this.injector);
|
||||
if (!this._ngElementStrategy) {
|
||||
const strategy = this._ngElementStrategy =
|
||||
strategyFactory.create(this.injector || config.injector);
|
||||
|
||||
// Collect pre-existing values on the element to re-apply through the strategy.
|
||||
const preExistingValues =
|
||||
|
@ -173,12 +174,10 @@ export function createCustomElement<P>(
|
|||
return this._ngElementStrategy!;
|
||||
}
|
||||
|
||||
private readonly injector: Injector;
|
||||
private _ngElementStrategy: NgElementStrategy|null = null;
|
||||
private _ngElementStrategy?: NgElementStrategy;
|
||||
|
||||
constructor(injector?: Injector) {
|
||||
constructor(private readonly injector?: Injector) {
|
||||
super();
|
||||
this.injector = injector || config.injector;
|
||||
}
|
||||
|
||||
attributeChangedCallback(
|
||||
|
|
|
@ -73,6 +73,26 @@ if (browserDetection.supportsCustomElements) {
|
|||
expect(strategy.getInputValue('barBar')).toBe('value-barbar');
|
||||
});
|
||||
|
||||
it('should work even if when the constructor is not called (due to polyfill)', () => {
|
||||
// Some polyfills (e.g. `document-register-element`) do not call the constructor of custom
|
||||
// elements. Currently, all the constructor does is initialize the `injector` property. This
|
||||
// test simulates not having called the constructor by "unsetting" the property.
|
||||
//
|
||||
// NOTE:
|
||||
// If the constructor implementation changes in the future, this test needs to be adjusted
|
||||
// accordingly.
|
||||
const element = new NgElementCtor(injector);
|
||||
delete (element as any).injector;
|
||||
|
||||
element.setAttribute('foo-foo', 'value-foo-foo');
|
||||
element.setAttribute('barbar', 'value-barbar');
|
||||
element.connectedCallback();
|
||||
|
||||
expect(strategy.connectedElement).toBe(element);
|
||||
expect(strategy.getInputValue('fooFoo')).toBe('value-foo-foo');
|
||||
expect(strategy.getInputValue('barBar')).toBe('value-barbar');
|
||||
});
|
||||
|
||||
it('should listen to output events after connected', () => {
|
||||
const element = new NgElementCtor(injector);
|
||||
element.connectedCallback();
|
||||
|
@ -260,7 +280,14 @@ if (browserDetection.supportsCustomElements) {
|
|||
class TestStrategyFactory implements NgElementStrategyFactory {
|
||||
testStrategy = new TestStrategy();
|
||||
|
||||
create(): NgElementStrategy {
|
||||
create(injector: Injector): NgElementStrategy {
|
||||
// Although not used by the `TestStrategy`, verify that the injector is provided.
|
||||
if (!injector) {
|
||||
throw new Error(
|
||||
'Expected injector to be passed to `TestStrategyFactory#create()`, but received ' +
|
||||
`value of type ${typeof injector}: ${injector}`);
|
||||
}
|
||||
|
||||
return this.testStrategy;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue