From 82da8e5d9bdda7f042d5d52ae658db83e5666b73 Mon Sep 17 00:00:00 2001 From: Chirayu Krishnappa Date: Mon, 18 Nov 2013 17:59:29 -0800 Subject: [PATCH] feat(scope): add support to skip auto digest in a turn Closes #235 --- lib/core/scope.dart | 37 ++++++++++++++++++++++++++++- lib/directive/ng_model.dart | 5 +++- test/core/scope_spec.dart | 47 ++++++++++++++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 3 deletions(-) diff --git a/lib/core/scope.dart b/lib/core/scope.dart index b3eede1db..da39f7144 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -55,6 +55,7 @@ class Scope implements Map { Map> _listeners = {}; Scope _nextSibling, _prevSibling, _childHead, _childTail; bool _isolate = false; + bool _skipAutoDigest = false; Profiler _perf; @@ -68,7 +69,7 @@ class Scope implements Map { _outerAsyncQueue = []; // Set up the zone to auto digest this scope. - _zone.onTurnDone = $digest; + _zone.onTurnDone = _autoDigestOnTurnDone; _zone.onError = (e, s, ls) => _exceptionHandler(e, s); } @@ -92,6 +93,14 @@ class Scope implements Map { } } + _autoDigestOnTurnDone() { + if (_skipAutoDigest) { + _skipAutoDigest = false; + } else { + $digest(); + } + } + _identical(a, b) => identical(a, b) || (a is String && b is String && a == b) || @@ -260,6 +269,7 @@ class Scope implements Map { * auto-digesting scope. */ $$verifyDigestWillRun() { + assert(!_skipAutoDigest); _zone.assertInTurn(); } @@ -395,6 +405,31 @@ class Scope implements Map { } + /** + * Skip running a $digest at the end of this turn. + * The primary use case is to skip the digest in the current VM turn because + * you just scheduled or are otherwise certain of an impending VM turn and the + * digest at the end of that turn is sufficient. You should be able to answer + * "No" to the question "Is there any other code that is aware that this VM + * turn occured and therefore expected a digest?". If your answer is "Yes", + * then you run the risk that the very next VM turn is not for your event and + * now that other code runs in that turn and sees stale values. + * + * You might call this function, for instance, from an event listener where, + * though the event occured, you need to wait for another event before you can + * perform something meaningful. You might schedule that other event, + * set a flag for the handler of the other event to recognize, etc. and then + * call this method to skip the digest this cycle. Note that you should call + * this function *after* you have successfully confirmed that the expected VM + * turn will occur (perhaps by scheduling it) to ensure that the digest + * actually does take place on that turn. + */ + $skipAutoDigest() { + _zone.assertInTurn(); + _skipAutoDigest = true; + } + + $apply([expr]) { return _zone.run(() { var timerId; diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index b763683e7..54185a142 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -109,7 +109,10 @@ abstract class _InputTextlikeDirective { inputElement.selectionEnd = end; }; inputElement.onChange.listen(relaxFnArgs(processValue)); - inputElement.onKeyDown.listen((e) => new async.Timer(Duration.ZERO, processValue)); + inputElement.onKeyDown.listen((e) { + new async.Timer(Duration.ZERO, processValue); + scope.$skipAutoDigest(); + }); } processValue() { diff --git a/test/core/scope_spec.dart b/test/core/scope_spec.dart index 6e5179623..73aa916ad 100644 --- a/test/core/scope_spec.dart +++ b/test/core/scope_spec.dart @@ -6,8 +6,13 @@ import 'dart:convert' show JSON; main() { describe(r'Scope', () { + NgZone zone; + + noop() {} + beforeEach(module(() { - return (NgZone zone) { + return (NgZone _zone) { + zone = _zone; zone.onError = (e, s, l) => null; }; })); @@ -74,6 +79,46 @@ main() { }); + describe(r'auto digest', () { + it(r'should auto digest at the end of the turn', inject((Scope $rootScope) { + var digestedValue = 0; + $rootScope.a = 1; + $rootScope.$watch('a', (newValue, oldValue, _this) { + digestedValue = newValue; + }); + expect(digestedValue).toEqual(0); + zone.run(noop); + expect(digestedValue).toEqual(1); + })); + + it(r'should skip auto digest if requested', inject((Scope $rootScope) { + var digestedValue = 0; + $rootScope.a = 1; + $rootScope.$watch('a', (newValue, oldValue, _this) { + digestedValue = newValue; + }); + expect(digestedValue).toEqual(0); + zone.run(() { + $rootScope.$skipAutoDigest(); + }); + expect(digestedValue).toEqual(0); + zone.run(noop); + expect(digestedValue).toEqual(1); + })); + + it(r'should throw exception if asked to skip auto digest outside of a turn', + inject((Scope $rootScope) { + var digestedValue = 0; + $rootScope.a = 1; + $rootScope.$watch('a', (newValue, oldValue, _this) { + digestedValue = newValue; + }); + expect(digestedValue).toEqual(0); + expect($rootScope.$skipAutoDigest).toThrow(); + })); + }); + + describe(r'$watch/$digest', () { it(r'should watch and fire on simple property change', inject((Scope $rootScope) { var log;