Skip to content

Commit 3213dcb

Browse files
authored
Merge pull request #1268 from glimmerjs/bugfix/fix-attribute-updating
[BUGFIX] Fix reactivity of dynamic attributes
2 parents 4b69798 + 988a21e commit 3213dcb

File tree

3 files changed

+58
-15
lines changed

3 files changed

+58
-15
lines changed

packages/@glimmer/integration-tests/test/attributes-test.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { normalizeProperty } from '@glimmer/runtime';
2-
import { assertElement, hasAttribute, jitSuite, RenderTest, test } from '..';
2+
import { assertElement, hasAttribute, jitSuite, RenderTest, test, tracked } from '..';
33
import { Namespace, SimpleElement } from '@simple-dom/interface';
4+
import { castToBrowser, expect } from '@glimmer/util';
45

56
export class AttributesTests extends RenderTest {
67
static suiteName = 'Attributes';
@@ -239,6 +240,40 @@ export class AttributesTests extends RenderTest {
239240
this.assertStableNodes();
240241
}
241242

243+
@test
244+
'handles successive updates to the same value'() {
245+
class Model {
246+
@tracked value = '';
247+
}
248+
249+
let model = new Model();
250+
251+
this.render('<input value={{this.model.value}} />', { model });
252+
this.assert.equal(this.readDOMAttr('value'), '');
253+
this.assertStableRerender();
254+
255+
let inputElement = castToBrowser(
256+
expect(this.element.firstChild, 'expected input to exist'),
257+
'input'
258+
);
259+
260+
inputElement.value = 'bar';
261+
this.assert.equal(this.readDOMAttr('value'), 'bar');
262+
263+
model.value = 'foo';
264+
this.rerender();
265+
this.assert.equal(this.readDOMAttr('value'), 'foo');
266+
this.assertStableNodes();
267+
268+
inputElement.value = 'bar';
269+
this.assert.equal(this.readDOMAttr('value'), 'bar');
270+
271+
model.value = 'foo';
272+
this.rerender();
273+
this.assert.equal(this.readDOMAttr('value'), 'foo');
274+
this.assertStableNodes();
275+
}
276+
242277
@test
243278
'input[checked] prop updates when set to undefined'() {
244279
this.registerHelper('if', (params) => {

packages/@glimmer/runtime/lib/compiled/opcodes/component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ function setDeferredAttr(
544544
.elements()
545545
.setDynamicAttribute(name, valueForRef(value), trusting, namespace);
546546
if (!isConstRef(value)) {
547-
vm.updateWith(new UpdateDynamicAttributeOpcode(value, attribute));
547+
vm.updateWith(new UpdateDynamicAttributeOpcode(value, attribute, vm.env));
548548
}
549549
}
550550
}

packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts

+21-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Reference, valueForRef, isConstRef } from '@glimmer/reference';
1+
import { Reference, valueForRef, isConstRef, createComputeRef } from '@glimmer/reference';
22
import { Revision, Tag, valueForTag, validateTag, consumeTag } from '@glimmer/validator';
33
import {
44
check,
@@ -17,6 +17,7 @@ import {
1717
Owner,
1818
CurriedType,
1919
ModifierDefinitionState,
20+
Environment,
2021
} from '@glimmer/interfaces';
2122
import { $t0 } from '@glimmer/vm';
2223
import { APPEND_OPCODES, UpdatingOpcode } from '../../opcodes';
@@ -237,27 +238,34 @@ APPEND_OPCODES.add(Op.DynamicAttr, (vm, { op1: _name, op2: _trusting, op3: _name
237238
let attribute = vm.elements().setDynamicAttribute(name, value, trusting, namespace);
238239

239240
if (!isConstRef(reference)) {
240-
vm.updateWith(new UpdateDynamicAttributeOpcode(reference, attribute));
241+
vm.updateWith(new UpdateDynamicAttributeOpcode(reference, attribute, vm.env));
241242
}
242243
});
243244

244245
export class UpdateDynamicAttributeOpcode extends UpdatingOpcode {
245246
public type = 'patch-element';
246247

247-
public lastValue: unknown;
248+
private updateRef: Reference;
248249

249-
constructor(private reference: Reference<unknown>, private attribute: DynamicAttribute) {
250+
constructor(reference: Reference<unknown>, attribute: DynamicAttribute, env: Environment) {
250251
super();
251-
this.lastValue = valueForRef(reference);
252-
}
253252

254-
evaluate(vm: UpdatingVM) {
255-
let { attribute, reference, lastValue } = this;
256-
let currentValue = valueForRef(reference);
253+
let initialized = false;
257254

258-
if (currentValue !== lastValue) {
259-
attribute.update(currentValue, vm.env);
260-
this.lastValue = currentValue;
261-
}
255+
this.updateRef = createComputeRef(() => {
256+
let value = valueForRef(reference);
257+
258+
if (initialized === true) {
259+
attribute.update(value, env);
260+
} else {
261+
initialized = true;
262+
}
263+
});
264+
265+
valueForRef(this.updateRef);
266+
}
267+
268+
evaluate() {
269+
valueForRef(this.updateRef);
262270
}
263271
}

0 commit comments

Comments
 (0)