diff --git a/lib/angular.dart b/lib/angular.dart
index 3529cf471..9806e4d97 100644
--- a/lib/angular.dart
+++ b/lib/angular.dart
@@ -171,7 +171,8 @@ bootstrapAngular(modules, [rootElementSelector = '[ng-app]']) {
Injector injector = new Injector(modules);
injector.invoke((Compiler $compile, Scope $rootScope) {
- $compile(topElt)(injector, topElt);
- $rootScope.$digest();
+ $rootScope.$apply(() {
+ $compile(topElt)(injector, topElt);
+ });
});
}
diff --git a/lib/block.dart b/lib/block.dart
index 5c336321b..17eca3db2 100644
--- a/lib/block.dart
+++ b/lib/block.dart
@@ -304,7 +304,6 @@ class ComponentFactory {
attachBlockToShadowDom(BlockType blockType) {
var block = blockType(shadowInjector);
shadowDom.nodes.addAll(block.elements);
- shadowInjector.get(Scope).$digest();
return shadowDom;
}
diff --git a/lib/directives/ng_include.dart b/lib/directives/ng_include.dart
index 6c87e3e3d..734a65a07 100644
--- a/lib/directives/ng_include.dart
+++ b/lib/directives/ng_include.dart
@@ -47,9 +47,6 @@ class NgIncludeAttrDirective {
// an url template
blockCache.fromUrl(value).then((createBlock) {
updateContent(createBlock);
-
- // Http should take care of this
- scope.$digest();
});
}
});
diff --git a/lib/scope.dart b/lib/scope.dart
index dbc1c7ac7..c5dc55891 100644
--- a/lib/scope.dart
+++ b/lib/scope.dart
@@ -173,6 +173,8 @@ class Scope implements Map {
}
+
+
$digest() {
var value, last,
asyncQueue = _asyncQueue,
@@ -283,20 +285,53 @@ class Scope implements Map {
$apply([expr]) {
- try {
- _beginPhase('\$apply');
- return $eval(expr);
- } catch (e, s) {
- _exceptionHandler(e, s);
- } finally {
- _clearPhase();
+ var toThrow;
+ var returnValue;
+ bool executingAsyncCallback = false;
+ async.runZonedExperimental(() {
try {
- $root.$digest();
+ _beginPhase('\$apply');
+ returnValue = $eval(expr);
} catch (e, s) {
_exceptionHandler(e, s);
- throw e;
+ } finally {
+ _clearPhase();
+ try {
+ $root.$digest();
+ } catch (e, s) {
+ _exceptionHandler(e, s);
+ throw e;
+ }
+ }
+ }, onRunAsync: (fn) {
+ async.runAsync(() {
+ // exceptions from fn() can not be caught here, but
+ // are handled by onError.
+ executingAsyncCallback = true;
+ fn();
+ executingAsyncCallback = false;
+ try {
+ $root.$digest();
+ } catch (e, s) {
+ _exceptionHandler(e, s);
+ }
+ });
+ },
+ onError: (e) {
+ if (executingAsyncCallback) {
+ // NOTE(deboer): Thrown strings don't have an attached stack trace
+ // http://dartbug.com/12000
+ _exceptionHandler(e, async.getAttachedStackTrace(e));
}
+ if (toThrow == null)
+ toThrow = e;
+ });
+ // This will only throw exceptions from the runZoned body.
+ // The async code will be executed after this check.
+ if (toThrow != null) {
+ throw toThrow;
}
+ return returnValue;
}
diff --git a/test/compiler_spec.dart b/test/compiler_spec.dart
index 1973738f4..b6eb2d860 100644
--- a/test/compiler_spec.dart
+++ b/test/compiler_spec.dart
@@ -412,8 +412,9 @@ main() {
$rootScope.sep = '-';
var element = $(r'
{{name}}{{sep}}{{$id}}:{{name}}{{sep}}{{$id}}
');
BlockType blockType = $compile(element);
- Block block = blockType(injector, element);
- $rootScope.$digest();
+ $rootScope.$apply(() {
+ Block block = blockType(injector, element);
+ });
nextTurn();
expect(element.textWithShadow()).toEqual('OUTTER-_1:INNER_2(OUTTER-_1)');
@@ -424,7 +425,9 @@ main() {
$rootScope.val = "poof";
var element = $('');
- $compile(element)(injector, element);
+ $rootScope.$apply(() {
+ $compile(element)(injector, element);
+ });
nextTurn();
expect(renderedText(element)).toEqual('inside poof');
@@ -432,7 +435,9 @@ main() {
it('should behave nicely if a mapped attribute is missing', async(inject(() {
var element = $('');
- $compile(element)(injector, element);
+ $rootScope.$apply(() {
+ $compile(element)(injector, element);
+ });
nextTurn();
expect(renderedText(element)).toEqual('inside ');
@@ -441,7 +446,9 @@ main() {
it('should behave nicely if a mapped attribute evals to null', async(inject(() {
$rootScope.val = null;
var element = $('');
- $compile(element)(injector, element);
+ $rootScope.$apply(() {
+ $compile(element)(injector, element);
+ });
nextTurn();
expect(renderedText(element)).toEqual('inside ');
@@ -491,10 +498,11 @@ main() {
}, throwsA(contains('No provider found for LocalAttrDirective! (resolving LocalAttrDirective)')));
}));
- it('should publish component controller into the scope', async(inject(() {
+ it('should publish component controller into the scope', async(inject((Scope $rootScope) {
var element = $(r'');
- $compile(element)(injector, element);
- $rootScope.$apply();
+ $rootScope.$apply(() {
+ $compile(element)(injector, element);
+ });
nextTurn();
expect(element.textWithShadow()).toEqual('WORKED');
diff --git a/test/scope_spec.dart b/test/scope_spec.dart
index 3c57cb01c..a14944a61 100644
--- a/test/scope_spec.dart
+++ b/test/scope_spec.dart
@@ -1,4 +1,5 @@
import "_specs.dart";
+import "dart:async";
main() {
describe(r'Scope', () {
@@ -482,6 +483,71 @@ main() {
});
});
+ describe(r'async', () {
+ beforeEach(module((AngularModule module) {
+ return module.type(ExceptionHandler, LogExceptionHandler);
+ }));
+
+ it(r'should run watch cycle on future resolution', async(inject((Scope scope) {
+ var log = '';
+ scope['r'] = 0;
+ scope['qq'] = 0;
+ scope.$watch('q', (_) {
+ log += 'q';
+ scope['qq'] = 1;
+ new Future.value('b').then((v) {
+ log += 'b';
+ scope['r'] = 3;
+ });
+ });
+
+ scope.$watch('r', (rVal) {
+ log += 'r';
+ scope['s'] = rVal * 2;
+ });
+ scope.$apply("q=2");
+ expect(scope['qq']).toEqual(1);
+ expect(scope['q']).toEqual(2);
+ expect(scope['r']).toEqual(0);
+ expect(log).toEqual('qr');
+ nextTurn(true);
+
+ expect(scope['r']).toEqual(3);
+ expect(scope['s']).toEqual(6);
+ expect(log).toEqual('qrbr');
+ })));
+
+
+ it(r'should catch exceptions from futures', async(inject((Scope scope, ExceptionHandler $exceptionHandler) {
+ scope.$watch('q', (_) {
+ scope['qq'] = 1;
+ new Future.value('b').then((v) {
+ throw "sadface";
+ });
+ });
+ scope.$apply("q=2");
+ nextTurn(true);
+ expect($exceptionHandler.errors.length).toEqual(1);
+ expect($exceptionHandler.errors[0].error).toEqual("sadface");
+ })));
+
+ it(r'should catch exceptions from digests trigger by futures', async(inject((Scope scope, ExceptionHandler $exceptionHandler) {
+ scope.$watch('q', (_) {
+ scope['qq'] = 1;
+ new Future.value('b').then((v) {
+ scope['x'] = 2;
+ });
+ });
+ scope.$watch('x', (xVal) {
+ if (xVal == 2) throw "x was 2";
+ });
+ scope.$apply("q=2");
+ nextTurn(true);
+ expect($exceptionHandler.errors.length).toEqual(1);
+ expect($exceptionHandler.errors[0].error).toEqual("x was 2");
+ })));
+ });
+
describe(r'exceptions', () {
var log;