diff --git a/_tests/test/core/application_ref_test.dart b/_tests/test/core/application_ref_test.dart index 8b0f6a4402..8610cf8259 100644 --- a/_tests/test/core/application_ref_test.dart +++ b/_tests/test/core/application_ref_test.dart @@ -12,11 +12,11 @@ import 'package:test/test.dart'; import 'application_ref_test.template.dart' as ng; void main() { - ApplicationRefImpl appRef; + ApplicationRef appRef; group('dispose should ', () { setUp(() { - appRef = new ApplicationRefImpl( + appRef = internalCreateApplicationRef( new NgZone(enableLongStackTrace: true), new Injector.map({ ExceptionHandler: const BrowserExceptionHandler(), diff --git a/angular/lib/src/bootstrap/modules.dart b/angular/lib/src/bootstrap/modules.dart index dd37aa7c90..dd0c6aa451 100644 --- a/angular/lib/src/bootstrap/modules.dart +++ b/angular/lib/src/bootstrap/modules.dart @@ -121,7 +121,11 @@ String createRandomAppId() { @experimental const bootstrapLegacyModule = const [ bootstrapMinimalModule, - const Provider(ApplicationRef, useClass: ApplicationRefImpl), + const Provider( + ApplicationRef, + useFactory: internalCreateApplicationRef, + deps: const [NgZone, Injector], + ), const Provider(AppViewUtils), const Provider(SlowComponentLoader), ]; diff --git a/angular/lib/src/bootstrap/run.dart b/angular/lib/src/bootstrap/run.dart index f4f96cf801..a3fc59981d 100644 --- a/angular/lib/src/bootstrap/run.dart +++ b/angular/lib/src/bootstrap/run.dart @@ -69,7 +69,7 @@ Injector appInjector(InjectorFactory userProvidedInjector) { // We also add other top-level services with similar constraints: // * `AppViewUtils` return ngZone.run(() { - applicationRef = new ApplicationRefImpl( + applicationRef = internalCreateApplicationRef( ngZone, userInjector, ); diff --git a/angular/lib/src/core/application_ref.dart b/angular/lib/src/core/application_ref.dart index 7e4e9f01de..d6d6ce6d2e 100644 --- a/angular/lib/src/core/application_ref.dart +++ b/angular/lib/src/core/application_ref.dart @@ -3,137 +3,140 @@ import 'dart:html'; import 'package:angular/src/core/change_detection/host.dart'; import 'package:angular/src/runtime.dart'; +import 'package:meta/dart2js.dart' as dart2js; import '../facade/exception_handler.dart' show ExceptionHandler; import 'change_detection/host.dart'; import 'di.dart'; import 'linker/component_factory.dart' show ComponentRef, ComponentFactory; import 'testability/testability.dart' show TestabilityRegistry, Testability; -import 'zone/ng_zone.dart' show NgZone, NgZoneError; +import 'zone/ng_zone.dart' show NgZone; -/// Create an Angular zone. -NgZone createNgZone() => new NgZone(enableLongStackTrace: isDevMode); +/// **INTERNAL ONLY**: Do not use. +@dart2js.tryInline +ApplicationRef internalCreateApplicationRef( + NgZone ngZone, + Injector injector, +) { + return new ApplicationRef._( + ngZone, + unsafeCast(injector.get(ExceptionHandler)), + injector, + ); +} /// A reference to an Angular application running on a page. /// /// For more about Angular applications, see the documentation for [bootstrap]. -abstract class ApplicationRef implements ChangeDetectionHost { - /// Register a listener to be called when the application is disposed. - void registerDisposeListener(void Function() listener); - - /// Bootstrap a new component at the root level of the application. - /// - /// When bootstrapping a new root component into an application, - /// Angular mounts the specified application component onto DOM elements - /// identified by the component's selector and kicks off automatic change - /// detection to finish initializing the component. - ComponentRef bootstrap(ComponentFactory componentFactory); - - /// Dispose of this application and all of its components. - void dispose(); -} +class ApplicationRef extends ChangeDetectionHost { + final List _disposeListeners = []; + final List> _rootComponents = []; -@Injectable() -class ApplicationRefImpl extends ApplicationRef with ChangeDetectionHost { - final NgZone _zone; + final ExceptionHandler _exceptionHandler; final Injector _injector; - final List _disposeListeners = []; - final List _rootComponents = []; - final List _streamSubscriptions = []; + final NgZone _ngZone; - ExceptionHandler _exceptionHandler; + StreamSubscription _onErrorSub; + StreamSubscription _onMicroSub; - ApplicationRefImpl(this._zone, this._injector) { - _zone.run(() { - _exceptionHandler = unsafeCast(_injector.get(ExceptionHandler)); - }); - _streamSubscriptions.add(_zone.onError.listen((NgZoneError error) { + ApplicationRef._( + this._ngZone, + this._exceptionHandler, + this._injector, + ) { + _onErrorSub = _ngZone.onError.listen((e) { handleUncaughtException( - error.error, - new StackTrace.fromString(error.stackTrace.join('\n')), + e.error, + new StackTrace.fromString(e.stackTrace.join('\n')), ); - })); - _streamSubscriptions.add(_zone.onMicrotaskEmpty.listen((_) { - _zone.runGuarded(() { - tick(); - }); - })); + }); + _onMicroSub = _ngZone.onMicrotaskEmpty.listen((_) { + _ngZone.runGuarded(tick); + }); } + + /// Register a listener to be called when the application is disposed. void registerDisposeListener(void Function() listener) { _disposeListeners.add(listener); } + /// Bootstrap a new component at the root level of the application. + /// + /// When bootstrapping a new root component into an application, + /// Angular mounts the specified application component onto DOM elements + /// identified by the component's selector and kicks off automatic change + /// detection to finish initializing the component. ComponentRef bootstrap(ComponentFactory componentFactory) { return unsafeCast(run(() { - var compRef = componentFactory.create(_injector, const []); - Element existingElement = - document.querySelector(componentFactory.selector); + final component = componentFactory.create(_injector); + final existing = querySelector(componentFactory.selector); Element replacement; - if (existingElement != null) { - Element newElement = compRef.location; + if (existing != null) { + final newElement = component.location; // For app shards using bootstrapStatic, transfer element id // from original node to allow hosting applications to locate loaded // application root. if (newElement.id == null || newElement.id.isEmpty) { - newElement.id = existingElement.id; + newElement.id = existing.id; } - existingElement.replaceWith(newElement); replacement = newElement; + existing.replaceWith(replacement); } else { - assert(compRef.location != null, - 'Could not locate node with selector ${componentFactory.selector}'); - document.body.append(compRef.location); + assert(component.location != null); + document.body.append(component.location); } - compRef.onDestroy(() { - _unloadComponent(compRef); - replacement?.remove(); - }); - var testability = compRef.injector.get(Testability, null); + final testability = unsafeCast( + component.injector.get(Testability, null), + ); if (testability != null) { - compRef.injector - .get(TestabilityRegistry) - .registerApplication(compRef.location, testability); + final registry = unsafeCast( + _injector.get(TestabilityRegistry), + ); + registry.registerApplication(component.location, testability); } - _loadComponent(compRef); - return compRef; + _loadedRootComponent(component, replacement); + return component; })); } - void _loadComponent(ComponentRef componentRef) { - registerChangeDetector(componentRef.changeDetectorRef); + void _loadedRootComponent(ComponentRef component, Element node) { + _rootComponents.add(component); + component.onDestroy(() { + _destroyedRootComponent(component); + node?.remove(); + }); + registerChangeDetector(component.changeDetectorRef); tick(); - _rootComponents.add(componentRef); } - void _unloadComponent(ComponentRef componentRef) { - if (!_rootComponents.contains(componentRef)) { + void _destroyedRootComponent(ComponentRef component) { + if (!_rootComponents.remove(component)) { return; } - unregisterChangeDetector(componentRef.changeDetectorRef); - _rootComponents.remove(componentRef); + unregisterChangeDetector(component.changeDetectorRef); } - @override + /// Dispose of this application and all of its components. void dispose() { - for (var ref in _rootComponents) { - ref.destroy(); - } - for (var dispose in _disposeListeners) { - dispose(); + _onErrorSub.cancel(); + _onMicroSub.cancel(); + for (final component in _rootComponents) { + component.destroy(); } - _disposeListeners.clear(); - for (var subscription in _streamSubscriptions) { - subscription.cancel(); + for (final listener in _disposeListeners) { + listener(); } - _streamSubscriptions.clear(); } @override - void handleUncaughtException(Object error, - [StackTrace trace, String reason]) { + void handleUncaughtException( + Object error, [ + StackTrace trace, + String reason, + ]) { _exceptionHandler.call(error, trace, reason); } @override - R runInZone(R Function() callback) => _zone.run(callback); + R runInZone(R Function() callback) => _ngZone.run(callback); } diff --git a/angular_test/lib/src/bootstrap.dart b/angular_test/lib/src/bootstrap.dart index f74d75f7da..b615dc4c17 100644 --- a/angular_test/lib/src/bootstrap.dart +++ b/angular_test/lib/src/bootstrap.dart @@ -51,7 +51,7 @@ Future> bootstrapForTest( } // This should be kept in sync with 'runApp' as much as possible. final injector = appInjector(userInjector); - final ApplicationRefImpl appRef = injector.get(ApplicationRef); + final ApplicationRef appRef = injector.get(ApplicationRef); NgZoneError caughtError; final NgZone ngZone = injector.get(NgZone); final onErrorSub = ngZone.onError.listen((e) { @@ -92,7 +92,7 @@ Future> bootstrapForTest( } Future> _runAndLoadComponent( - ApplicationRefImpl appRef, + ApplicationRef appRef, ComponentFactory componentFactory, Element hostElement, Injector injector, {