Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit 0e12949

Browse files
committed
fix(compiler): OneWayOneTime bindings now wait for the model to stablize
BREAKING CHANGE Previously, OneWayOneTime bindings would remove their watch after the first value assignment. For models that take more than one digest loop to stablize, this meant the OneWayOneTime bindings took a un-stablized value. With this change, these bindings will continue to accept value assignments until their stablized value is non-null. The assignment may occur multiple times. We expect that most uses of OneWayOneTime will be 'ok' with being called a few times as the model stablizes. However, applications which depend on OneWayOneTime bindings being called exactly once will break. The suggested upgrade path is to move the senstive code into a domWrite callback. e.g. the old code: @decorator( map: const { 'one-time': '=>!value' }) class OneTime { set value(v) => onlyCalledOnce(v); } becomes @decorator( map: const { 'one-time': '=>!value' }) class OneTime { var _value; set value(v) { if (_value == null) { scope.rootScope.domWrite(() => onlyCalledOnce(_value)); } _value = v; } } Closes #1013
1 parent 5ad9c7a commit 0e12949

File tree

2 files changed

+82
-3
lines changed

2 files changed

+82
-3
lines changed

Diff for: lib/core_dom/element_binder.dart

+11-2
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,18 @@ class ElementBinder {
176176

177177
Expression attrExprFn = _parser(nodeAttrs[attrName]);
178178
var watch;
179+
var lastOneTimeValue;
179180
watch = scope.watch(nodeAttrs[attrName], (value, _) {
180-
if (dstPathFn.assign(controller, value) != null) {
181-
watch.remove();
181+
if ((lastOneTimeValue = dstPathFn.assign(controller, value)) != null && watch != null) {
182+
var watchToRemove = watch;
183+
watch = null;
184+
scope.rootScope.domWrite(() {
185+
if (lastOneTimeValue != null) {
186+
watchToRemove.remove();
187+
} else { // It was set to non-null, but stablized to null, wait.
188+
watch = watchToRemove;
189+
}
190+
});
182191
}
183192
}, formatters: formatters);
184193
break;

Diff for: test/core_dom/compiler_spec.dart

+71-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@ void main() {
269269
..bind(SometimesComponent)
270270
..bind(ExprAttrComponent)
271271
..bind(LogElementComponent)
272-
..bind(SayHelloFormatter);
272+
..bind(SayHelloFormatter)
273+
..bind(OneTimeDecorator);
273274
});
274275

275276
it('should select on element', async(() {
@@ -690,6 +691,64 @@ void main() {
690691
}
691692
}));
692693
});
694+
695+
describe('bindings', () {
696+
it('should set a one-time binding with the correct value', (Logger logger) {
697+
_.compile(r'<div one-time="v"></div>');
698+
699+
_.rootScope.context['v'] = 1;
700+
701+
var context = _.rootScope.context;
702+
_.rootScope.watch('3+4', (v, _) => context['v'] = v);
703+
704+
// In the 1st digest iteration:
705+
// v will be set to 7
706+
// OneTimeDecorator.value will be set to 1
707+
// In the 2nd digest iteration:
708+
// OneTimeDecorator.value will be set to 7
709+
_.rootScope.apply();
710+
711+
expect(logger).toEqual([1, 7]);
712+
});
713+
714+
it('should keep one-time binding until it is set to non-null', (Logger logger) {
715+
_.compile(r'<div one-time="v"></div>');
716+
_.rootScope.context['v'] = null;
717+
_.rootScope.apply();
718+
expect(logger).toEqual([null]);
719+
720+
_.rootScope.context['v'] = 7;
721+
_.rootScope.apply();
722+
expect(logger).toEqual([null, 7]);
723+
724+
// Check that the binding is removed.
725+
_.rootScope.context['v'] = 8;
726+
_.rootScope.apply();
727+
expect(logger).toEqual([null, 7]);
728+
});
729+
730+
it('should remove the one-time binding only if it stablizied to null', (Logger logger) {
731+
_.compile(r'<div one-time="v"></div>');
732+
733+
_.rootScope.context['v'] = 1;
734+
735+
var context = _.rootScope.context;
736+
_.rootScope.watch('3+4', (v, _) => context['v'] = null);
737+
738+
_.rootScope.apply();
739+
expect(logger).toEqual([1, null]);
740+
741+
// Even though there was a null in the unstable model, we shouldn't remove the binding
742+
context['v'] = 8;
743+
_.rootScope.apply();
744+
expect(logger).toEqual([1, null, 8]);
745+
746+
// Check that the binding is removed.
747+
_.rootScope.context['v'] = 9;
748+
_.rootScope.apply();
749+
expect(logger).toEqual([1, null, 8]);
750+
});
751+
});
693752
});
694753

695754

@@ -1130,3 +1189,14 @@ class LogElementComponent{
11301189
logger(shadowRoot);
11311190
}
11321191
}
1192+
1193+
@Decorator(
1194+
selector: '[one-time]',
1195+
map: const {
1196+
'one-time': '=>!value'
1197+
})
1198+
class OneTimeDecorator {
1199+
Logger log;
1200+
OneTimeDecorator(this.log);
1201+
set value(v) => log(v);
1202+
}

0 commit comments

Comments
 (0)