Skip to content

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 dart-archive#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)
    dart-archive#2      NgView._show.<anonymous closure> (package:angular/routing/ng_view.dart:106:15)
    dart-archive#3      _rootRunUnary (dart:async/zone.dart:730)
    dart-archive#4      _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    dart-archive#5      _onRunUnary.<anonymous closure> (package:angular/core/zone.dart:113:63)
    dart-archive#6      VmTurnZone._onRunBase (package:angular/core/zone.dart:97:16)
    dart-archive#7      _onRunUnary (package:angular/core/zone.dart:113:17)
    dart-archive#8      _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    dart-archive#9      _CustomizedZone.runUnary (dart:async/zone.dart:667)
    dart-archive#10     _BaseZone.runUnaryGuarded (dart:async/zone.dart:582)
    dart-archive#11     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:333)
    dart-archive#12     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:263)
    dart-archive#13     _SyncBroadcastStreamController._sendData.<anonymous closure> (dart:async/broadcast_stream_controller.dart:344)
    dart-archive#14     _BroadcastStreamController._forEachListener (dart:async/broadcast_stream_controller.dart:297)
    dart-archive#15     _SyncBroadcastStreamController._sendData (dart:async/broadcast_stream_controller.dart:343)
    dart-archive#16     _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:227)
    dart-archive#17     Router._leaveCurrentRouteHelper (package:route_hierarchical/client.dart:654:48)
    dart-archive#18     Router._leaveCurrentRouteHelper (package:route_hierarchical/client.dart:656:47)
    dart-archive#19     Router._leaveCurrentRoute (package:route_hierarchical/client.dart:645:41)
    dart-archive#20     Router._leaveOldRoutes (package:route_hierarchical/client.dart:525:30)
    dart-archive#21     Router._processNewRoute (package:route_hierarchical/client.dart:497:27)
    dart-archive#22     Router._route.<anonymous closure> (package:route_hierarchical/client.dart:481:29)
    dart-archive#23     _rootRunUnary (dart:async/zone.dart:730)
    dart-archive#24     _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    dart-archive#25     _onRunUnary.<anonymous closure> (package:angular/core/zone.dart:113:63)
    dart-archive#26     VmTurnZone._onRunBase (package:angular/core/zone.dart:97:16)
    dart-archive#27     _onRunUnary (package:angular/core/zone.dart:113:17)
    dart-archive#28     _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    dart-archive#29     _CustomizedZone.runUnary (dart:async/zone.dart:667)
    dart-archive#30     _Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:488)
    dart-archive#31     _Future._propagateToListeners (dart:async/future_impl.dart:571)
    dart-archive#32     _Future._completeWithValue (dart:async/future_impl.dart:331)
    dart-archive#33     _Future._asyncComplete.<anonymous closure> (dart:async/future_impl.dart:393)
    dart-archive#34     _rootRun (dart:async/zone.dart:723)
    dart-archive#35     _ZoneDelegate.run (dart:async/zone.dart:453)
    dart-archive#36     _onScheduleMicrotask.<anonymous closure> (package:angular/core/zone.dart:117:43)
    dart-archive#37     VmTurnZone._finishTurn (package:angular/core/zone.dart:143:34)
    dart-archive#38     VmTurnZone._onRunBase (package:angular/core/zone.dart:104:43)
    dart-archive#39     _onRunUnary (package:angular/core/zone.dart:113:17)
    dart-archive#40     _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    dart-archive#41     _CustomizedZone.runUnary (dart:async/zone.dart:667)
    dart-archive#42     _BaseZone.runUnaryGuarded (dart:async/zone.dart:582)
    dart-archive#43     _BaseZone.bindUnaryCallback.<anonymous closure> (dart:async/zone.dart:608)
  • Loading branch information
chirayuk committed Jun 26, 2014
1 parent 462217f commit 6004c34
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 8 deletions.
5 changes: 4 additions & 1 deletion lib/routing/ng_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,10 @@ class NgView implements DetachAware, RouteProvider {
if (_view == null) return;

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

if (_childScope.isAttached) {
_childScope.destroy();
}

_view = null;
_childScope = null;
Expand Down
40 changes: 33 additions & 7 deletions test/routing/ng_view_spec.dart
Original file line number Diff line number Diff line change
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 @@ -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 6004c34

Please sign in to comment.