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

Commit 46c671f

Browse files
committed
feat(scope): Automatically $digest futures inside of $apply.
1 parent 78aa95c commit 46c671f

File tree

6 files changed

+153
-23
lines changed

6 files changed

+153
-23
lines changed

lib/angular.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ bootstrapAngular(modules, [rootElementSelector = '[ng-app]']) {
166166
Injector injector = new Injector(modules);
167167

168168
injector.invoke((Compiler $compile, Scope $rootScope) {
169-
$compile(topElt)(injector, topElt);
170-
$rootScope.$digest();
169+
$rootScope.$apply(() {
170+
$compile(topElt)(injector, topElt);
171+
});
171172
});
172173
}

lib/block.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,6 @@ class _ComponentFactory {
303303
attachBlockToShadowDom(BlockFactory blockFactory) {
304304
var block = blockFactory(shadowInjector);
305305
shadowDom.nodes.addAll(block.elements);
306-
shadowInjector.get(Scope).$digest();
307306
return shadowDom;
308307
}
309308

lib/directives/ng_include.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ class NgIncludeAttrDirective {
4747
// an url template
4848
blockCache.fromUrl(value).then((createBlock) {
4949
updateContent(createBlock);
50-
51-
// Http should take care of this
52-
scope.$digest();
5350
});
5451
}
5552
});

lib/scope.dart

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ class Scope implements Map {
173173
}
174174

175175

176+
177+
176178
$digest() {
177179
var value, last,
178180
asyncQueue = _asyncQueue,
@@ -283,20 +285,53 @@ class Scope implements Map {
283285

284286

285287
$apply([expr]) {
286-
try {
287-
_beginPhase('\$apply');
288-
return $eval(expr);
289-
} catch (e, s) {
290-
_exceptionHandler(e, s);
291-
} finally {
292-
_clearPhase();
288+
var toThrow;
289+
var returnValue;
290+
bool executingAsyncCallback = false;
291+
async.runZonedExperimental(() {
293292
try {
294-
$root.$digest();
293+
_beginPhase('\$apply');
294+
returnValue = $eval(expr);
295295
} catch (e, s) {
296296
_exceptionHandler(e, s);
297-
throw e;
297+
} finally {
298+
_clearPhase();
299+
try {
300+
$root.$digest();
301+
} catch (e, s) {
302+
_exceptionHandler(e, s);
303+
throw e;
304+
}
305+
}
306+
}, onRunAsync: (fn) {
307+
async.runAsync(() {
308+
// exceptions from fn() can not be caught here, but
309+
// are handled by onError.
310+
executingAsyncCallback = true;
311+
fn();
312+
executingAsyncCallback = false;
313+
try {
314+
$root.$digest();
315+
} catch (e, s) {
316+
_exceptionHandler(e, s);
317+
}
318+
});
319+
},
320+
onError: (e) {
321+
if (executingAsyncCallback) {
322+
// NOTE(deboer): Thrown strings don't have an attached stack trace
323+
// http://dartbug.com/12000
324+
_exceptionHandler(e, async.getAttachedStackTrace(e));
298325
}
326+
if (toThrow == null)
327+
toThrow = e;
328+
});
329+
// This will only throw exceptions from the runZoned body.
330+
// The async code will be executed after this check.
331+
if (toThrow != null) {
332+
throw toThrow;
299333
}
334+
return returnValue;
300335
}
301336

302337

test/compiler_spec.dart

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,9 @@ main() {
232232
$rootScope.sep = '-';
233233
var element = $(r'<div>{{name}}{{sep}}{{$id}}:<simple>{{name}}{{sep}}{{$id}}</simple></div>');
234234
BlockFactory blockFactory = $compile(element);
235-
Block block = blockFactory(injector, element);
236-
$rootScope.$digest();
235+
$rootScope.$apply(() {
236+
Block block = blockFactory(injector, element);
237+
});
237238

238239
nextTurn();
239240
expect(element.textWithShadow()).toEqual('OUTTER-_1:INNER_2(OUTTER-_1)');
@@ -244,15 +245,19 @@ main() {
244245
$rootScope.val = "poof";
245246
var element = $('<parent-expression from-parent=val></parent-expression>');
246247

247-
$compile(element)(injector, element);
248+
$rootScope.$apply(() {
249+
$compile(element)(injector, element);
250+
});
248251

249252
nextTurn();
250253
expect(renderedText(element)).toEqual('inside poof');
251254
})));
252255

253256
it('should behave nicely if a mapped attribute is missing', async(inject(() {
254257
var element = $('<parent-expression></parent-expression>');
255-
$compile(element)(injector, element);
258+
$rootScope.$apply(() {
259+
$compile(element)(injector, element);
260+
});
256261

257262
nextTurn();
258263
expect(renderedText(element)).toEqual('inside ');
@@ -261,7 +266,9 @@ main() {
261266
it('should behave nicely if a mapped attribute evals to null', async(inject(() {
262267
$rootScope.val = null;
263268
var element = $('<parent-expression fromParent=val></parent-expression>');
264-
$compile(element)(injector, element);
269+
$rootScope.$apply(() {
270+
$compile(element)(injector, element);
271+
});
265272

266273
nextTurn();
267274
expect(renderedText(element)).toEqual('inside ');
@@ -311,10 +318,11 @@ main() {
311318
}, throwsA(contains('No provider found for LocalAttrDirective! (resolving LocalAttrDirective)')));
312319
}));
313320

314-
it('should publish component controller into the scope', async(inject(() {
321+
it('should publish component controller into the scope', async(inject((Scope $rootScope) {
315322
var element = $(r'<div><publish-me></publish-me></div>');
316-
$compile(element)(injector, element);
317-
$rootScope.$apply();
323+
$rootScope.$apply(() {
324+
$compile(element)(injector, element);
325+
});
318326

319327
nextTurn();
320328
expect(element.textWithShadow()).toEqual('WORKED');

test/scope_spec.dart

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import "_specs.dart";
2+
import "dart:async";
23

34
main() {
45
describe(r'Scope', () {
@@ -482,6 +483,95 @@ main() {
482483
});
483484
});
484485

486+
describe(r'async', () {
487+
beforeEach(module((AngularModule module) {
488+
return module.type(ExceptionHandler, LogExceptionHandler);
489+
}));
490+
491+
492+
it(r'should run watch cycle on future resolution', async(inject((Scope scope) {
493+
var log = '';
494+
scope['r'] = 0;
495+
scope['qq'] = 0;
496+
scope.$watch('q', (_) {
497+
log += 'q';
498+
scope['qq'] = 1;
499+
new Future.value('b').then((v) {
500+
log += 'b';
501+
scope['r'] = 3;
502+
});
503+
});
504+
505+
scope.$watch('r', (rVal) {
506+
log += 'r';
507+
scope['s'] = rVal * 2;
508+
});
509+
scope.$apply("q=2");
510+
expect(scope['qq']).toEqual(1);
511+
expect(scope['q']).toEqual(2);
512+
expect(scope['r']).toEqual(0);
513+
expect(log).toEqual('qr');
514+
nextTurn(true);
515+
516+
expect(scope['r']).toEqual(3);
517+
expect(scope['s']).toEqual(6);
518+
expect(log).toEqual('qrbr');
519+
})));
520+
521+
522+
it(r'should catch exceptions from futures', async(inject((Scope scope, ExceptionHandler $exceptionHandler) {
523+
scope.$watch('q', (_) {
524+
scope['qq'] = 1;
525+
new Future.value('b').then((v) {
526+
throw "sadface";
527+
});
528+
});
529+
scope.$apply("q=2");
530+
nextTurn(true);
531+
expect($exceptionHandler.errors.length).toEqual(1);
532+
expect($exceptionHandler.errors[0].error).toEqual("sadface");
533+
})));
534+
535+
536+
it(r'should catch exceptions from digests trigger by futures', async(inject((Scope scope, ExceptionHandler $exceptionHandler) {
537+
scope.$watch('q', (_) {
538+
scope['qq'] = 1;
539+
new Future.value('b').then((v) {
540+
scope['x'] = 2;
541+
});
542+
});
543+
scope.$watch('x', (xVal) {
544+
if (xVal == 2) throw "x was 2";
545+
});
546+
scope.$apply("q=2");
547+
nextTurn(true);
548+
expect($exceptionHandler.errors.length).toEqual(1);
549+
expect($exceptionHandler.errors[0].error).toEqual("x was 2");
550+
})));
551+
552+
553+
it(r'should throw from the synchronous code', () {
554+
module((AngularModule module) {
555+
return module.type(ExceptionHandler, ExceptionHandler);
556+
});
557+
558+
async(inject((Scope scope, ExceptionHandler $exceptionHandler) {
559+
scope.$watch('q', (_) {
560+
scope['qq'] = 1;
561+
new Future.value('b').then((v) {
562+
scope['x'] = 2;
563+
});
564+
throw "sync throw";
565+
});
566+
expect(() {
567+
scope.$apply("q=2");
568+
nextTurn(true);
569+
}).toThrow('sync throw');
570+
571+
}));
572+
});
573+
});
574+
485575

486576
describe(r'exceptions', () {
487577
var log;

0 commit comments

Comments
 (0)