Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions packages/elements/src/component-factory-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
this.implementsOnChanges =
isFunction((this.componentRef.instance as any as OnChanges).ngOnChanges);

this.initializeInputs();
this.initializeInputs(element);
this.initializeOutputs();

this.detectChanges();
Expand All @@ -162,9 +162,16 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
}

/** Set any stored initial inputs on the component's properties. */
protected initializeInputs(): void {
protected initializeInputs(element?: HTMLElement): void {
this.componentFactory.inputs.forEach(({propName}) => {
const initialValue = this.initialInputValues.get(propName);
let initialValue;
// Remove values set before upgrade to unshadow setter/getter
if(element && element.hasOwnProperty(propName)){
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be cleaner/safer if this "property upgrade" happens as soon as possible (i.e. inside the strategy constructor). For example, this allows handling setting values via both properties and attributes to remain in sync. (As it is now, if a property is set before upgrading and then the corresponding attribute is set after upgrading (but before connecting), the property value will be used, despite the attribute being set later.)

This would require the element to be passed to the strategy constructor though, which might be a breaking change 😞

BTW, we should also ensure that the strategy is instantiated before using it in the input getters/setters, because it is not guaranteed to be.

initialValue = (element as {})[propName as keyof {}];
delete (element as {})[propName as keyof {}];
this.initialInputValues.set(propName, initialValue);
}
initialValue = this.initialInputValues.get(propName);
if (initialValue) {
this.setInputValue(propName, initialValue);
} else {
Expand Down
13 changes: 12 additions & 1 deletion packages/elements/test/create-custom-element_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
import {Subject} from 'rxjs';

import {NgElementConstructor, createCustomElement} from '../src/create-custom-element';
import {NgElementConstructor, createCustomElement, NgElement} from '../src/create-custom-element';
import {NgElementStrategy, NgElementStrategyEvent, NgElementStrategyFactory} from '../src/element-strategy';

type WithFooBar = {
Expand Down Expand Up @@ -94,6 +94,17 @@ if (browserDetection.supportsCustomElements) {
expect(strategy.inputs.get('fooFoo')).toBe('foo-foo-value');
expect(strategy.inputs.get('barBar')).toBe('barBar-value');
});

it('should properly handle initial values on the element', () => {
const element = document.createElement('lazy-test-element') as NgElement & WithFooBar;
element.fooFoo = 'foo-foo-value';
expect(element.hasOwnProperty('fooFoo')).toBe(true);
customElements.define('lazy-test-element', createCustomElement(TestComponent, {injector}));
document.body.appendChild(element);
expect(element.hasOwnProperty('fooFoo')).toBe(false);
expect(element.fooFoo).toBe('foo-foo-value');

});
});
}

Expand Down