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

Commit 98b9832

Browse files
committed
fix(ng-view): cleanup should not destroy an already destroyed scope
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
1 parent 05e2c57 commit 98b9832

File tree

2 files changed

+36
-10
lines changed

2 files changed

+36
-10
lines changed

lib/routing/ng_view.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class NgView implements DetachAware, RouteProvider {
9090
void detach() {
9191
_route.discard();
9292
_locationService._unregisterPortal(this);
93+
_cleanUp();
9394
}
9495

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

129130
_view.nodes.forEach((node) => node.remove());
130131
_childScope.destroy();
131-
132132
_view = null;
133133
_childScope = null;
134134
}

test/routing/ng_view_spec.dart

+35-9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import '../_specs.dart';
55
import 'package:angular/routing/module.dart';
66
import 'package:angular/mock/module.dart';
77

8-
main() {
8+
main() => describe('ngView', () {
99
describe('Flat ngView', () {
1010
TestBed _;
1111
Router router;
@@ -98,42 +98,65 @@ main() {
9898
beforeEach((TestBed tb, Router _router, TemplateCache templates) {
9999
_ = tb;
100100
router = _router;
101+
_.rootScope.context['flag'] = true;
101102

102103
templates.put('library.html', new HttpResponse(200,
103104
'<div><h1>Library</h1>'
104-
'<ng-view></ng-view></div>'));
105+
'<ng-view ng-if="flag"></ng-view></div>'));
105106
templates.put('book_list.html', new HttpResponse(200,
106107
'<h1>Books</h1>'));
107108
templates.put('book_overview.html', new HttpResponse(200,
108109
'<h2>Book 1234</h2>'));
109110
templates.put('book_read.html', new HttpResponse(200,
110111
'<h2>Read Book 1234</h2>'));
112+
templates.put('alt.html', new HttpResponse(200, 'alt'));
111113
});
112114

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

117120
router.route('/library/all');
118-
microLeap();
121+
microLeap(); _.rootScope.apply(); microLeap();
119122
expect(root.text).toEqual('LibraryBooks');
120123

121124
router.route('/library/1234');
122-
microLeap();
125+
microLeap(); _.rootScope.apply(); microLeap();
123126
expect(root.text).toEqual('LibraryBook 1234');
124127

125128
// nothing should change here
126129
router.route('/library/1234/overview');
127-
microLeap();
130+
microLeap(); _.rootScope.apply(); microLeap();
128131
expect(root.text).toEqual('LibraryBook 1234');
129132

130133
// nothing should change here
131134
router.route('/library/1234/read');
132-
microLeap();
135+
microLeap(); _.rootScope.apply(); microLeap();
133136
expect(root.text).toEqual('LibraryRead Book 1234');
134137
}));
135-
});
136138

139+
it('should not attempt to destroy and already destroyed childscope', async(() {
140+
// This can happen with nested ng-views. Refer
141+
// https://github.com/angular/angular.dart/issues/1182
142+
// and repro case
143+
// https://github.com/chirayuk/sample/tree/issue_1182_leaving_a_nested_ng_view
144+
Element root = _.compile('<ng-view></ng-view>');
145+
microLeap(); _.rootScope.apply(); microLeap();
146+
147+
router.route('/library/1234');
148+
microLeap(); _.rootScope.apply(); microLeap();
149+
150+
expect(root.text).toEqual('LibraryBook 1234');
151+
152+
_.rootScope.context['flag'] = false;
153+
microLeap(); _.rootScope.apply(); microLeap();
154+
router.route('/alt');
155+
microLeap(); _.rootScope.apply(); microLeap();
156+
157+
expect(root.text).toEqual('alt');
158+
}));
159+
});
137160

138161
describe('Inline template ngView', () {
139162
TestBed _;
@@ -165,7 +188,7 @@ main() {
165188
expect(root.text).toEqual('Hello');
166189
}));
167190
});
168-
}
191+
});
169192

170193
class FlatRouteInitializer implements Function {
171194
void call(Router router, RouteViewFactory views) {
@@ -193,7 +216,10 @@ class NestedRouteInitializer implements Function {
193216
'read': ngRoute(path: '/read', view: 'book_read.html'),
194217
'admin': ngRoute(path: '/admin', view: 'admin.html'),
195218
})
196-
})
219+
}),
220+
'alt': ngRoute(
221+
path: '/alt',
222+
view: 'alt.html'),
197223
});
198224
}
199225
}

0 commit comments

Comments
 (0)