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

Commit

Permalink
fix(ng-view): cleanup should not destroy an already destroyed scope
Browse files Browse the repository at this point in the history
Refer #1182
and repro https://github.com/chirayuk/sample/tree/issue_1182_leaving_a_nested_ng_view

NgView's register cleanup handlers this way:

    _leaveSubscription = route.onLeave.listen((_) {
      _leaveSubscription.cancel();
      // …
      _cleanUp();
    });

When there are nested ng-views, upon a route change, the parent NgView
calls it's _cleanUp() first (which destroys it's child scope) and then
the child NgView attempts a cleanup.  However, it's child scope is
already detached due to the parent NgView cleaning up causing an
exception.

Stack trace is:

    'package:angular/core/scope.dart': Failed assertion: line 335 pos 12: 'isAttached' is not true.

    STACKTRACE:
    #0      Scope.destroy (package:angular/core/scope.dart:335:12)
    #1      NgView._cleanUp (package:angular/routing/ng_view.dart:130:24)
    #2      NgView._show.<anonymous closure> (package:angular/routing/ng_view.dart:106:15)
    #3      _rootRunUnary (dart:async/zone.dart:730)
    #4      _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    #5      _onRunUnary.<anonymous closure> (package:angular/core/zone.dart:113:63)
    #6      VmTurnZone._onRunBase (package:angular/core/zone.dart:97:16)
    #7      _onRunUnary (package:angular/core/zone.dart:113:17)
    #8      _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    #9      _CustomizedZone.runUnary (dart:async/zone.dart:667)
    #10     _BaseZone.runUnaryGuarded (dart:async/zone.dart:582)
    #11     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:333)
    #12     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:263)
    #13     _SyncBroadcastStreamController._sendData.<anonymous closure> (dart:async/broadcast_stream_controller.dart:344)
    #14     _BroadcastStreamController._forEachListener (dart:async/broadcast_stream_controller.dart:297)
    #15     _SyncBroadcastStreamController._sendData (dart:async/broadcast_stream_controller.dart:343)
    #16     _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:227)
    #17     Router._leaveCurrentRouteHelper (package:route_hierarchical/client.dart:654:48)
    #18     Router._leaveCurrentRouteHelper (package:route_hierarchical/client.dart:656:47)
    #19     Router._leaveCurrentRoute (package:route_hierarchical/client.dart:645:41)
    #20     Router._leaveOldRoutes (package:route_hierarchical/client.dart:525:30)
    #21     Router._processNewRoute (package:route_hierarchical/client.dart:497:27)
    #22     Router._route.<anonymous closure> (package:route_hierarchical/client.dart:481:29)
    #23     _rootRunUnary (dart:async/zone.dart:730)
    #24     _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    #25     _onRunUnary.<anonymous closure> (package:angular/core/zone.dart:113:63)
    #26     VmTurnZone._onRunBase (package:angular/core/zone.dart:97:16)
    #27     _onRunUnary (package:angular/core/zone.dart:113:17)
    #28     _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    #29     _CustomizedZone.runUnary (dart:async/zone.dart:667)
    #30     _Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:488)
    #31     _Future._propagateToListeners (dart:async/future_impl.dart:571)
    #32     _Future._completeWithValue (dart:async/future_impl.dart:331)
    #33     _Future._asyncComplete.<anonymous closure> (dart:async/future_impl.dart:393)
    #34     _rootRun (dart:async/zone.dart:723)
    #35     _ZoneDelegate.run (dart:async/zone.dart:453)
    #36     _onScheduleMicrotask.<anonymous closure> (package:angular/core/zone.dart:117:43)
    #37     VmTurnZone._finishTurn (package:angular/core/zone.dart:143:34)
    #38     VmTurnZone._onRunBase (package:angular/core/zone.dart:104:43)
    #39     _onRunUnary (package:angular/core/zone.dart:113:17)
    #40     _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    #41     _CustomizedZone.runUnary (dart:async/zone.dart:667)
    #42     _BaseZone.runUnaryGuarded (dart:async/zone.dart:582)
    #43     _BaseZone.bindUnaryCallback.<anonymous closure> (dart:async/zone.dart:608)

Closes #1182
  • Loading branch information
chirayuk committed Jun 26, 2014
1 parent 05e2c57 commit 98b9832
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 10 deletions.
2 changes: 1 addition & 1 deletion lib/routing/ng_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class NgView implements DetachAware, RouteProvider {
void detach() {
_route.discard();
_locationService._unregisterPortal(this);
_cleanUp();
}

void _show(_View viewDef, Route route, List<Module> modules) {
Expand Down Expand Up @@ -128,7 +129,6 @@ class NgView implements DetachAware, RouteProvider {

_view.nodes.forEach((node) => node.remove());
_childScope.destroy();

_view = null;
_childScope = null;
}
Expand Down
44 changes: 35 additions & 9 deletions test/routing/ng_view_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import '../_specs.dart';
import 'package:angular/routing/module.dart';
import 'package:angular/mock/module.dart';

main() {
main() => describe('ngView', () {
describe('Flat ngView', () {
TestBed _;
Router router;
Expand Down Expand Up @@ -98,42 +98,65 @@ main() {
beforeEach((TestBed tb, Router _router, TemplateCache templates) {
_ = tb;
router = _router;
_.rootScope.context['flag'] = true;

templates.put('library.html', new HttpResponse(200,
'<div><h1>Library</h1>'
'<ng-view></ng-view></div>'));
'<ng-view ng-if="flag"></ng-view></div>'));
templates.put('book_list.html', new HttpResponse(200,
'<h1>Books</h1>'));
templates.put('book_overview.html', new HttpResponse(200,
'<h2>Book 1234</h2>'));
templates.put('book_read.html', new HttpResponse(200,
'<h2>Read Book 1234</h2>'));
templates.put('alt.html', new HttpResponse(200, 'alt'));
});

it('should switch nested templates', async(() {
Element root = _.compile('<ng-view></ng-view>');
microLeap(); _.rootScope.apply(); microLeap();
expect(root.text).toEqual('');

router.route('/library/all');
microLeap();
microLeap(); _.rootScope.apply(); microLeap();
expect(root.text).toEqual('LibraryBooks');

router.route('/library/1234');
microLeap();
microLeap(); _.rootScope.apply(); microLeap();
expect(root.text).toEqual('LibraryBook 1234');

// nothing should change here
router.route('/library/1234/overview');
microLeap();
microLeap(); _.rootScope.apply(); microLeap();
expect(root.text).toEqual('LibraryBook 1234');

// nothing should change here
router.route('/library/1234/read');
microLeap();
microLeap(); _.rootScope.apply(); microLeap();
expect(root.text).toEqual('LibraryRead Book 1234');
}));
});

it('should not attempt to destroy and already destroyed childscope', async(() {
// This can happen with nested ng-views. Refer
// https://github.com/angular/angular.dart/issues/1182
// and repro case
// https://github.com/chirayuk/sample/tree/issue_1182_leaving_a_nested_ng_view
Element root = _.compile('<ng-view></ng-view>');
microLeap(); _.rootScope.apply(); microLeap();

router.route('/library/1234');
microLeap(); _.rootScope.apply(); microLeap();

expect(root.text).toEqual('LibraryBook 1234');

_.rootScope.context['flag'] = false;
microLeap(); _.rootScope.apply(); microLeap();
router.route('/alt');
microLeap(); _.rootScope.apply(); microLeap();

expect(root.text).toEqual('alt');
}));
});

describe('Inline template ngView', () {
TestBed _;
Expand Down Expand Up @@ -165,7 +188,7 @@ main() {
expect(root.text).toEqual('Hello');
}));
});
}
});

class FlatRouteInitializer implements Function {
void call(Router router, RouteViewFactory views) {
Expand Down Expand Up @@ -193,7 +216,10 @@ class NestedRouteInitializer implements Function {
'read': ngRoute(path: '/read', view: 'book_read.html'),
'admin': ngRoute(path: '/admin', view: 'admin.html'),
})
})
}),
'alt': ngRoute(
path: '/alt',
view: 'alt.html'),
});
}
}

0 comments on commit 98b9832

Please sign in to comment.