Skip to content

Commit 0c1ecb4

Browse files
crisbetoalxhub
authored andcommitted
fix(elements): not setting initial value on signal-based input (#59773)
Fixes that `createCustomElement` was incorrectly excluding signal-based inputs when setting the initial values. Fixes #59757. PR Close #59773
1 parent e635f42 commit 0c1ecb4

File tree

2 files changed

+39
-5
lines changed

2 files changed

+39
-5
lines changed

packages/elements/src/create-custom-element.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,13 @@ export function createCustomElement<P>(
156156
// Re-apply pre-existing input values (set as properties on the element) through the
157157
// strategy.
158158
// TODO(alxhub): why are we doing this? this makes no sense.
159-
inputs.forEach(({propName, transform, isSignal}) => {
160-
if (!this.hasOwnProperty(propName) || isSignal) {
161-
// No pre-existing value for `propName`, or a signal input.
159+
inputs.forEach(({propName, transform}) => {
160+
if (!this.hasOwnProperty(propName)) {
161+
// No pre-existing value for `propName`.
162162
return;
163163
}
164164

165-
// Delete the property from the instance and re-apply it through the strategy.
165+
// Delete the property from the DOM node and re-apply it through the strategy.
166166
const value = (this as any)[propName];
167167
delete (this as any)[propName];
168168
strategy.setInputValue(propName, value, transform);

packages/elements/test/create-custom-element_spec.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
DoBootstrap,
1313
EventEmitter,
1414
Injector,
15+
input,
1516
Input,
1617
NgModule,
1718
Output,
@@ -31,6 +32,7 @@ interface WithFooBar {
3132
fooFoo: string;
3233
barBar: string;
3334
fooTransformed: unknown;
35+
fooSignal: string | null;
3436
}
3537

3638
describe('createCustomElement', () => {
@@ -66,20 +68,27 @@ describe('createCustomElement', () => {
6668
});
6769

6870
it('should use a default strategy for converting component inputs', () => {
69-
expect(NgElementCtor.observedAttributes).toEqual(['foo-foo', 'barbar', 'foo-transformed']);
71+
expect(NgElementCtor.observedAttributes).toEqual([
72+
'foo-foo',
73+
'barbar',
74+
'foo-transformed',
75+
'foo-signal',
76+
]);
7077
});
7178

7279
it('should send input values from attributes when connected', () => {
7380
const element = new NgElementCtor(injector);
7481
element.setAttribute('foo-foo', 'value-foo-foo');
7582
element.setAttribute('barbar', 'value-barbar');
7683
element.setAttribute('foo-transformed', 'truthy');
84+
element.setAttribute('foo-signal', 'value-signal');
7785
element.connectedCallback();
7886
expect(strategy.connectedElement).toBe(element);
7987

8088
expect(strategy.getInputValue('fooFoo')).toBe('value-foo-foo');
8189
expect(strategy.getInputValue('barBar')).toBe('value-barbar');
8290
expect(strategy.getInputValue('fooTransformed')).toBe(true);
91+
expect(strategy.getInputValue('fooSignal')).toBe('value-signal');
8392
});
8493

8594
it('should work even if the constructor is not called (due to polyfill)', () => {
@@ -95,12 +104,14 @@ describe('createCustomElement', () => {
95104
element.setAttribute('foo-foo', 'value-foo-foo');
96105
element.setAttribute('barbar', 'value-barbar');
97106
element.setAttribute('foo-transformed', 'truthy');
107+
element.setAttribute('foo-signal', 'value-signal');
98108
element.connectedCallback();
99109

100110
expect(strategy.connectedElement).toBe(element);
101111
expect(strategy.getInputValue('fooFoo')).toBe('value-foo-foo');
102112
expect(strategy.getInputValue('barBar')).toBe('value-barbar');
103113
expect(strategy.getInputValue('fooTransformed')).toBe(true);
114+
expect(strategy.getInputValue('fooSignal')).toBe('value-signal');
104115
});
105116

106117
it('should listen to output events after connected', () => {
@@ -174,10 +185,12 @@ describe('createCustomElement', () => {
174185
element.fooFoo = 'foo-foo-value';
175186
element.barBar = 'barBar-value';
176187
element.fooTransformed = 'truthy';
188+
element.fooSignal = 'value-signal';
177189

178190
expect(strategy.inputs.get('fooFoo')).toBe('foo-foo-value');
179191
expect(strategy.inputs.get('barBar')).toBe('barBar-value');
180192
expect(strategy.inputs.get('fooTransformed')).toBe(true);
193+
expect(strategy.inputs.get('fooSignal')).toBe('value-signal');
181194
});
182195

183196
it('should properly handle getting/setting properties on the element even if the constructor is not called', () => {
@@ -191,10 +204,12 @@ describe('createCustomElement', () => {
191204
element.fooFoo = 'foo-foo-value';
192205
element.barBar = 'barBar-value';
193206
element.fooTransformed = 'truthy';
207+
element.fooSignal = 'value-signal';
194208

195209
expect(strategy.inputs.get('fooFoo')).toBe('foo-foo-value');
196210
expect(strategy.inputs.get('barBar')).toBe('barBar-value');
197211
expect(strategy.inputs.get('fooTransformed')).toBe(true);
212+
expect(strategy.inputs.get('fooSignal')).toBe('value-signal');
198213
});
199214

200215
it('should capture properties set before upgrading the element', () => {
@@ -204,21 +219,25 @@ describe('createCustomElement', () => {
204219
fooFoo: 'foo-prop-value',
205220
barBar: 'bar-prop-value',
206221
fooTransformed: 'truthy' as unknown,
222+
fooSignal: 'value-signal',
207223
});
208224
expect(element.fooFoo).toBe('foo-prop-value');
209225
expect(element.barBar).toBe('bar-prop-value');
210226
expect(element.fooTransformed).toBe('truthy');
227+
expect(element.fooSignal).toBe('value-signal');
211228

212229
// Upgrade the element to a Custom Element and insert it into the DOM.
213230
customElements.define(selector, ElementCtor);
214231
testContainer.appendChild(element);
215232
expect(element.fooFoo).toBe('foo-prop-value');
216233
expect(element.barBar).toBe('bar-prop-value');
217234
expect(element.fooTransformed).toBe(true);
235+
expect(element.fooSignal).toBe('value-signal');
218236

219237
expect(strategy.inputs.get('fooFoo')).toBe('foo-prop-value');
220238
expect(strategy.inputs.get('barBar')).toBe('bar-prop-value');
221239
expect(strategy.inputs.get('fooTransformed')).toBe(true);
240+
expect(strategy.inputs.get('fooSignal')).toBe('value-signal');
222241
});
223242

224243
it('should capture properties set after upgrading the element but before inserting it into the DOM', () => {
@@ -228,30 +247,36 @@ describe('createCustomElement', () => {
228247
fooFoo: 'foo-prop-value',
229248
barBar: 'bar-prop-value',
230249
fooTransformed: 'truthy' as unknown,
250+
fooSignal: 'value-signal',
231251
});
232252
expect(element.fooFoo).toBe('foo-prop-value');
233253
expect(element.barBar).toBe('bar-prop-value');
234254
expect(element.fooTransformed).toBe('truthy');
255+
expect(element.fooSignal).toBe('value-signal');
235256

236257
// Upgrade the element to a Custom Element (without inserting it into the DOM) and update a
237258
// property.
238259
customElements.define(selector, ElementCtor);
239260
customElements.upgrade(element);
240261
element.barBar = 'bar-prop-value-2';
241262
element.fooTransformed = '';
263+
element.fooSignal = 'value-signal-changed';
242264
expect(element.fooFoo).toBe('foo-prop-value');
243265
expect(element.barBar).toBe('bar-prop-value-2');
244266
expect(element.fooTransformed).toBe('');
267+
expect(element.fooSignal).toBe('value-signal-changed');
245268

246269
// Insert the element into the DOM.
247270
testContainer.appendChild(element);
248271
expect(element.fooFoo).toBe('foo-prop-value');
249272
expect(element.barBar).toBe('bar-prop-value-2');
250273
expect(element.fooTransformed).toBe(false);
274+
expect(element.fooSignal).toBe('value-signal-changed');
251275

252276
expect(strategy.inputs.get('fooFoo')).toBe('foo-prop-value');
253277
expect(strategy.inputs.get('barBar')).toBe('bar-prop-value-2');
254278
expect(strategy.inputs.get('fooTransformed')).toBe(false);
279+
expect(strategy.inputs.get('fooSignal')).toBe('value-signal-changed');
255280
});
256281

257282
it('should allow overwriting properties with attributes after upgrading the element but before inserting it into the DOM', () => {
@@ -261,10 +286,12 @@ describe('createCustomElement', () => {
261286
fooFoo: 'foo-prop-value',
262287
barBar: 'bar-prop-value',
263288
fooTransformed: 'truthy' as unknown,
289+
fooSignal: 'value-signal',
264290
});
265291
expect(element.fooFoo).toBe('foo-prop-value');
266292
expect(element.barBar).toBe('bar-prop-value');
267293
expect(element.fooTransformed).toBe('truthy');
294+
expect(element.fooSignal).toBe('value-signal');
268295

269296
// Upgrade the element to a Custom Element (without inserting it into the DOM) and set an
270297
// attribute.
@@ -275,16 +302,19 @@ describe('createCustomElement', () => {
275302
expect(element.fooFoo).toBe('foo-prop-value');
276303
expect(element.barBar).toBe('bar-attr-value');
277304
expect(element.fooTransformed).toBe(false);
305+
expect(element.fooSignal).toBe('value-signal');
278306

279307
// Insert the element into the DOM.
280308
testContainer.appendChild(element);
281309
expect(element.fooFoo).toBe('foo-prop-value');
282310
expect(element.barBar).toBe('bar-attr-value');
283311
expect(element.fooTransformed).toBe(false);
312+
expect(element.fooSignal).toBe('value-signal');
284313

285314
expect(strategy.inputs.get('fooFoo')).toBe('foo-prop-value');
286315
expect(strategy.inputs.get('barBar')).toBe('bar-attr-value');
287316
expect(strategy.inputs.get('fooTransformed')).toBe(false);
317+
expect(strategy.inputs.get('fooSignal')).toBe('value-signal');
288318
});
289319

290320
// Helpers
@@ -313,6 +343,10 @@ describe('createCustomElement', () => {
313343
@Input('barbar') barBar!: string;
314344
@Input({transform: (value: unknown) => !!value}) fooTransformed!: boolean;
315345

346+
// This needs to apply the decorator and pass `isSignal`, because
347+
// the compiler transform doesn't run against JIT tests.
348+
@Input({isSignal: true} as Input) fooSignal = input<string | null>(null);
349+
316350
@Output() bazBaz = new EventEmitter<boolean>();
317351
@Output('quxqux') quxQux = new EventEmitter<Object>();
318352
}

0 commit comments

Comments
 (0)