From 327980bf4964c948e4d53aa3d0262a2e8320d6af Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 20 May 2020 11:10:20 +0300 Subject: [PATCH] fix(elements): correctly handle getting/setting properties before connecting the element (#36114) `createCustomElements()` creates some getters/setters for properties corresponding to component inputs that delegate to the `NgElementStrategy`. However, it is not guaranteed that the element's `NgElementStrategy` will have been created when these getters/setters are called, because some polyfills (e.g. `document-register-element`) do not call the constructor. Previously, trying to get/set input properties before connecting the element to the DOM (via `connectedCallback()`) would fail due to `NgElementStrategy` not being created. This commit ensures that the `NgElementStrategy` is always created before used inside the input property getters/setters (similar to how it is done for other methods of `NgElement`). Mentioned in https://github.com/angular/angular/pull/31416/files#r300326698. PR Close #36114 --- packages/elements/src/create-custom-element.ts | 6 ++++++ .../elements/test/create-custom-element_spec.ts | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/packages/elements/src/create-custom-element.ts b/packages/elements/src/create-custom-element.ts index 79763b63de..93d5dabc2b 100644 --- a/packages/elements/src/create-custom-element.ts +++ b/packages/elements/src/create-custom-element.ts @@ -187,9 +187,15 @@ export function createCustomElement

( inputs.map(({propName}) => propName).forEach(property => { Object.defineProperty(NgElementImpl.prototype, property, { get: function() { + if (!this.ngElementStrategy) { + this.ngElementStrategy = strategyFactory.create(config.injector); + } return this.ngElementStrategy.getInputValue(property); }, set: function(newValue: any) { + if (!this.ngElementStrategy) { + this.ngElementStrategy = strategyFactory.create(config.injector); + } this.ngElementStrategy.setInputValue(property, newValue); }, configurable: true, diff --git a/packages/elements/test/create-custom-element_spec.ts b/packages/elements/test/create-custom-element_spec.ts index 450e1eed53..913a5aa1ff 100644 --- a/packages/elements/test/create-custom-element_spec.ts +++ b/packages/elements/test/create-custom-element_spec.ts @@ -94,6 +94,22 @@ if (browserDetection.supportsCustomElements) { expect(strategy.inputs.get('fooFoo')).toBe('foo-foo-value'); expect(strategy.inputs.get('barBar')).toBe('barBar-value'); }); + + it('should properly handle getting/setting properties on the element even if the constructor is not called', + () => { + // Create a custom element while ensuring that the `NgElementStrategy` is not created + // inside the constructor. This is done to emulate the behavior of some polyfills that do + // not call the constructor. + strategyFactory.create = () => undefined as unknown as NgElementStrategy; + const element = new NgElementCtor(injector); + strategyFactory.create = TestStrategyFactory.prototype.create; + + element.fooFoo = 'foo-foo-value'; + element.barBar = 'barBar-value'; + + expect(strategy.inputs.get('fooFoo')).toBe('foo-foo-value'); + expect(strategy.inputs.get('barBar')).toBe('barBar-value'); + }); }); }