Revert "fix(elements): capture input properties set before upgrading the element (#36114)"
This reverts commit 1c8f1799d494254074e71c460db47dd65f58575a because it is causing the side effects test to break on the 9.1.x branch.
This commit is contained in:
parent
12d8af50dd
commit
6f3157fe6d
@ -145,32 +145,9 @@ export function createCustomElement<P>(
|
|||||||
// TODO(andrewseguin): Add e2e tests that cover cases where the constructor isn't called. For
|
// 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.
|
// now this is tested using a Google internal test suite.
|
||||||
if (this._ngElementStrategy === null) {
|
if (this._ngElementStrategy === null) {
|
||||||
const strategy = this._ngElementStrategy = strategyFactory.create(this.injector);
|
this._ngElementStrategy = strategyFactory.create(this.injector);
|
||||||
|
|
||||||
// Collect pre-existing values on the element to re-apply through the strategy.
|
|
||||||
const preExistingValues =
|
|
||||||
inputs.filter(({propName}) => this.hasOwnProperty(propName)).map(({propName}): [
|
|
||||||
string, any
|
|
||||||
] => [propName, (this as any)[propName]]);
|
|
||||||
|
|
||||||
// In some browsers (e.g. IE10), `Object.setPrototypeOf()` (which is required by some Custom
|
|
||||||
// Elements polyfills) is not defined and is thus polyfilled in a way that does not preserve
|
|
||||||
// the prototype chain. In such cases, `this` will not be an instance of `NgElementImpl` and
|
|
||||||
// thus not have the component input getters/setters defined on `NgElementImpl.prototype`.
|
|
||||||
if (!(this instanceof NgElementImpl)) {
|
|
||||||
// Add getters and setters to the instance itself for each property input.
|
|
||||||
defineInputGettersSetters(inputs, this);
|
|
||||||
} else {
|
|
||||||
// Delete the property from the instance, so that it can go through the getters/setters
|
|
||||||
// set on `NgElementImpl.prototype`.
|
|
||||||
preExistingValues.forEach(([propName]) => delete (this as any)[propName]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-apply pre-existing values through the strategy.
|
|
||||||
preExistingValues.forEach(([propName, value]) => strategy.setInputValue(propName, value));
|
|
||||||
}
|
}
|
||||||
|
return this._ngElementStrategy;
|
||||||
return this._ngElementStrategy!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly injector: Injector;
|
private readonly injector: Injector;
|
||||||
@ -216,26 +193,16 @@ export function createCustomElement<P>(
|
|||||||
// Update the property descriptor of `NgElementImpl#ngElementStrategy` to make it enumerable.
|
// Update the property descriptor of `NgElementImpl#ngElementStrategy` to make it enumerable.
|
||||||
Object.defineProperty(NgElementImpl.prototype, 'ngElementStrategy', {enumerable: true});
|
Object.defineProperty(NgElementImpl.prototype, 'ngElementStrategy', {enumerable: true});
|
||||||
|
|
||||||
// Add getters and setters to the prototype for each property input.
|
// Add getters and setters to the prototype for each property input. If the config does not
|
||||||
defineInputGettersSetters(inputs, NgElementImpl.prototype);
|
// contain property inputs, use all inputs by default.
|
||||||
|
inputs.map(({propName}) => propName).forEach(property => {
|
||||||
return (NgElementImpl as any) as NgElementConstructor<P>;
|
Object.defineProperty(NgElementImpl.prototype, property, {
|
||||||
}
|
get: function() { return this.ngElementStrategy.getInputValue(property); },
|
||||||
|
set: function(newValue: any) { this.ngElementStrategy.setInputValue(property, newValue); },
|
||||||
// Helpers
|
|
||||||
function defineInputGettersSetters(
|
|
||||||
inputs: {propName: string, templateName: string}[], target: object): void {
|
|
||||||
// Add getters and setters for each property input.
|
|
||||||
inputs.forEach(({propName}) => {
|
|
||||||
Object.defineProperty(target, propName, {
|
|
||||||
get(): any {
|
|
||||||
return this.ngElementStrategy.getInputValue(propName);
|
|
||||||
},
|
|
||||||
set(newValue: any): void {
|
|
||||||
this.ngElementStrategy.setInputValue(propName, newValue);
|
|
||||||
},
|
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return (NgElementImpl as any) as NgElementConstructor<P>;
|
||||||
}
|
}
|
||||||
|
@ -22,16 +22,12 @@ type WithFooBar = {
|
|||||||
|
|
||||||
if (browserDetection.supportsCustomElements) {
|
if (browserDetection.supportsCustomElements) {
|
||||||
describe('createCustomElement', () => {
|
describe('createCustomElement', () => {
|
||||||
let selectorUid = 0;
|
|
||||||
let testContainer: HTMLDivElement;
|
|
||||||
let NgElementCtor: NgElementConstructor<WithFooBar>;
|
let NgElementCtor: NgElementConstructor<WithFooBar>;
|
||||||
let strategy: TestStrategy;
|
let strategy: TestStrategy;
|
||||||
let strategyFactory: TestStrategyFactory;
|
let strategyFactory: TestStrategyFactory;
|
||||||
let injector: Injector;
|
let injector: Injector;
|
||||||
|
|
||||||
beforeAll(done => {
|
beforeAll(done => {
|
||||||
testContainer = document.createElement('div');
|
|
||||||
document.body.appendChild(testContainer);
|
|
||||||
destroyPlatform();
|
destroyPlatform();
|
||||||
platformBrowserDynamic()
|
platformBrowserDynamic()
|
||||||
.bootstrapModule(TestModule)
|
.bootstrapModule(TestModule)
|
||||||
@ -40,23 +36,18 @@ if (browserDetection.supportsCustomElements) {
|
|||||||
strategyFactory = new TestStrategyFactory();
|
strategyFactory = new TestStrategyFactory();
|
||||||
strategy = strategyFactory.testStrategy;
|
strategy = strategyFactory.testStrategy;
|
||||||
|
|
||||||
const {selector, ElementCtor} = createTestCustomElement();
|
NgElementCtor = createCustomElement(TestComponent, {injector, strategyFactory});
|
||||||
NgElementCtor = ElementCtor;
|
|
||||||
|
|
||||||
// The `@webcomponents/custom-elements/src/native-shim.js` polyfill allows us to create
|
// The `@webcomponents/custom-elements/src/native-shim.js` polyfill allows us to create
|
||||||
// new instances of the NgElement which extends HTMLElement, as long as we define it.
|
// new instances of the NgElement which extends HTMLElement, as long as we define it.
|
||||||
customElements.define(selector, NgElementCtor);
|
customElements.define('test-element', NgElementCtor);
|
||||||
})
|
})
|
||||||
.then(done, done.fail);
|
.then(done, done.fail);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => strategy.reset());
|
afterEach(() => strategy.reset());
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => destroyPlatform());
|
||||||
destroyPlatform();
|
|
||||||
document.body.removeChild(testContainer);
|
|
||||||
(testContainer as any) = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use a default strategy for converting component inputs', () => {
|
it('should use a default strategy for converting component inputs', () => {
|
||||||
expect(NgElementCtor.observedAttributes).toEqual(['foo-foo', 'barbar']);
|
expect(NgElementCtor.observedAttributes).toEqual(['foo-foo', 'barbar']);
|
||||||
@ -122,90 +113,7 @@ if (browserDetection.supportsCustomElements) {
|
|||||||
expect(strategy.inputs.get('barBar')).toBe('barBar-value');
|
expect(strategy.inputs.get('barBar')).toBe('barBar-value');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should capture properties set before upgrading the element', () => {
|
|
||||||
// Create a regular element and set properties on it.
|
|
||||||
const {selector, ElementCtor} = createTestCustomElement();
|
|
||||||
const element = Object.assign(document.createElement(selector), {
|
|
||||||
fooFoo: 'foo-prop-value',
|
|
||||||
barBar: 'bar-prop-value',
|
|
||||||
});
|
|
||||||
expect(element.fooFoo).toBe('foo-prop-value');
|
|
||||||
expect(element.barBar).toBe('bar-prop-value');
|
|
||||||
|
|
||||||
// Upgrade the element to a Custom Element and insert it into the DOM.
|
|
||||||
customElements.define(selector, ElementCtor);
|
|
||||||
testContainer.appendChild(element);
|
|
||||||
expect(element.fooFoo).toBe('foo-prop-value');
|
|
||||||
expect(element.barBar).toBe('bar-prop-value');
|
|
||||||
|
|
||||||
expect(strategy.inputs.get('fooFoo')).toBe('foo-prop-value');
|
|
||||||
expect(strategy.inputs.get('barBar')).toBe('bar-prop-value');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should capture properties set after upgrading the element but before inserting it into the DOM',
|
|
||||||
() => {
|
|
||||||
// Create a regular element and set properties on it.
|
|
||||||
const {selector, ElementCtor} = createTestCustomElement();
|
|
||||||
const element = Object.assign(document.createElement(selector), {
|
|
||||||
fooFoo: 'foo-prop-value',
|
|
||||||
barBar: 'bar-prop-value',
|
|
||||||
});
|
|
||||||
expect(element.fooFoo).toBe('foo-prop-value');
|
|
||||||
expect(element.barBar).toBe('bar-prop-value');
|
|
||||||
|
|
||||||
// Upgrade the element to a Custom Element (without inserting it into the DOM) and update a
|
|
||||||
// property.
|
|
||||||
customElements.define(selector, ElementCtor);
|
|
||||||
customElements.upgrade(element);
|
|
||||||
element.barBar = 'bar-prop-value-2';
|
|
||||||
expect(element.fooFoo).toBe('foo-prop-value');
|
|
||||||
expect(element.barBar).toBe('bar-prop-value-2');
|
|
||||||
|
|
||||||
// Insert the element into the DOM.
|
|
||||||
testContainer.appendChild(element);
|
|
||||||
expect(element.fooFoo).toBe('foo-prop-value');
|
|
||||||
expect(element.barBar).toBe('bar-prop-value-2');
|
|
||||||
|
|
||||||
expect(strategy.inputs.get('fooFoo')).toBe('foo-prop-value');
|
|
||||||
expect(strategy.inputs.get('barBar')).toBe('bar-prop-value-2');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow overwriting properties with attributes after upgrading the element but before inserting it into the DOM',
|
|
||||||
() => {
|
|
||||||
// Create a regular element and set properties on it.
|
|
||||||
const {selector, ElementCtor} = createTestCustomElement();
|
|
||||||
const element = Object.assign(document.createElement(selector), {
|
|
||||||
fooFoo: 'foo-prop-value',
|
|
||||||
barBar: 'bar-prop-value',
|
|
||||||
});
|
|
||||||
expect(element.fooFoo).toBe('foo-prop-value');
|
|
||||||
expect(element.barBar).toBe('bar-prop-value');
|
|
||||||
|
|
||||||
// Upgrade the element to a Custom Element (without inserting it into the DOM) and set an
|
|
||||||
// attribute.
|
|
||||||
customElements.define(selector, ElementCtor);
|
|
||||||
customElements.upgrade(element);
|
|
||||||
element.setAttribute('barbar', 'bar-attr-value');
|
|
||||||
expect(element.fooFoo).toBe('foo-prop-value');
|
|
||||||
expect(element.barBar).toBe('bar-attr-value');
|
|
||||||
|
|
||||||
// Insert the element into the DOM.
|
|
||||||
testContainer.appendChild(element);
|
|
||||||
expect(element.fooFoo).toBe('foo-prop-value');
|
|
||||||
expect(element.barBar).toBe('bar-attr-value');
|
|
||||||
|
|
||||||
expect(strategy.inputs.get('fooFoo')).toBe('foo-prop-value');
|
|
||||||
expect(strategy.inputs.get('barBar')).toBe('bar-attr-value');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
function createTestCustomElement() {
|
|
||||||
return {
|
|
||||||
selector: `test-element-${++selectorUid}`,
|
|
||||||
ElementCtor: createCustomElement<WithFooBar>(TestComponent, {injector, strategyFactory}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'test-component',
|
selector: 'test-component',
|
||||||
template: 'TestComponent|foo({{ fooFoo }})|bar({{ barBar }})',
|
template: 'TestComponent|foo({{ fooFoo }})|bar({{ barBar }})',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user