diff --git a/lib/core/scope.dart b/lib/core/scope.dart index 303270dfa..b0335ad9f 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -173,19 +173,43 @@ class Scope { * controller code you will most likely use [watch]. */ Watch watch(expression, ReactionFn reactionFn, {context, FilterMap filters}) { - assert(expression != null); - AST ast = expression is AST - ? expression - : rootScope._astParser(expression, context: context, filters: filters); - return watchGroup.watch(ast, reactionFn); + return _watch(watchGroup, expression, reactionFn, context, filters); } Watch observe(expression, ReactionFn reactionFn, {context, FilterMap filters}) { + return _watch(observeGroup, expression, reactionFn, context, filters); + } + + Watch _watch(WatchGroup group, expression, ReactionFn reactionFn, + context, FilterMap filters) { assert(expression != null); - AST ast = expression is AST - ? expression - : rootScope._astParser(expression, context: context, filters: filters); - return observeGroup.watch(ast, reactionFn); + AST ast; + Watch watch; + ReactionFn fn = reactionFn; + if (expression is AST) { + ast = expression; + } else if (expression is String) { + if (expression.startsWith('::')) { + expression = expression.substring(2); + fn = (value, last) { + if (value != null) { + watch.remove(); + return reactionFn(value, last); + } + }; + } else if (expression.startsWith(':')) { + expression = expression.substring(1); + fn = (value, last) { + if (value != null) { + return reactionFn(value, last); + } + }; + } + ast = rootScope._astParser(expression, context: context, filters: filters); + } else { + throw 'expressions must be String or AST got $expression.'; + } + return watch = group.watch(ast, fn); } dynamic eval(expression, [Map locals]) { diff --git a/test/core/scope_spec.dart b/test/core/scope_spec.dart index 8ae8d5218..3e58a4521 100644 --- a/test/core/scope_spec.dart +++ b/test/core/scope_spec.dart @@ -984,6 +984,34 @@ main() => describe('scope', () { })); }); + + describe('special binding modes', () { + it('should bind one time', inject((RootScope rootScope, Logger log) { + rootScope.watch('foo', (v, _) => log('foo:$v')); + rootScope.watch(':foo', (v, _) => log(':foo:$v')); + rootScope.watch('::foo', (v, _) => log('::foo:$v')); + + rootScope.apply(); + expect(log).toEqual(['foo:null']); + log.clear(); + + rootScope.context['foo'] = true; + rootScope.apply(); + expect(log).toEqual(['foo:true', ':foo:true', '::foo:true']); + log.clear(); + + rootScope.context['foo'] = 123; + rootScope.apply(); + expect(log).toEqual(['foo:123', ':foo:123']); + log.clear(); + + rootScope.context['foo'] = null; + rootScope.apply(); + expect(log).toEqual(['foo:null']); + log.clear(); + })); + }); + describe('runAsync', () { it(r'should run callback before watch', inject((RootScope rootScope) {