Skip to content
This repository was archived by the owner on Apr 15, 2025. It is now read-only.

Commit 6004c34

Browse files
committed
fix(ng-view): cleanup should not destroy an already destroyed scope
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)
1 parent 462217f commit 6004c34

File tree

2 files changed

+37
-8
lines changed

2 files changed

+37
-8
lines changed

lib/routing/ng_view.dart

+4-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ class NgView implements DetachAware, RouteProvider {
127127
if (_view == null) return;
128128

129129
_view.nodes.forEach((node) => node.remove());
130-
_childScope.destroy();
130+
131+
if (_childScope.isAttached) {
132+
_childScope.destroy();
133+
}
131134

132135
_view = null;
133136
_childScope = null;

test/routing/ng_view_spec.dart

+33-7
Original file line numberDiff line numberDiff line change
@@ -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 _;
@@ -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)