refactor(core): support non reflective bootstrap.

This changes Angular so that it can be used without reflection (assuming a codegen for injectors).

BREAKIKNG CHANGE:
- Drops `APP_COMPONENT` provider. Instead, inject
  `ApplicationRef` and read its `componentTypes` property.
- long form bootstrap has changed into the following:
  ```
  var platform = createPlatform(ReflectiveInjector.resolveAndCreate(BROWSER_PROVIDERS));
  var appInjector =
    ReflectiveInjector.resolveAndCreate([BROWSER_APP_PROVIDERS, appProviders], platform.injector);
  coreLoadAndBootstrap(appInjector, MyApp);
  ```
This commit is contained in:
Tobias Bosch
2016-04-14 14:52:35 -07:00
parent 0a7d10ba55
commit 9092ac79d4
73 changed files with 784 additions and 649 deletions
+143 -105
View File
@@ -14,131 +14,169 @@ import {
inject,
SpyObject
} from 'angular2/testing_internal';
import {Type} from 'angular2/src/facade/lang';
import {SpyChangeDetectorRef} from './spies';
import {ApplicationRef_, ApplicationRef, PlatformRef_} from "angular2/src/core/application_ref";
import {Injector, Provider, APP_INITIALIZER} from "angular2/core";
import {
ApplicationRef_,
ApplicationRef,
PLATFORM_CORE_PROVIDERS,
APPLICATION_CORE_PROVIDERS
} from "angular2/src/core/application_ref";
import {
Injector,
Provider,
APP_INITIALIZER,
Component,
ReflectiveInjector,
coreLoadAndBootstrap,
coreBootstrap,
PlatformRef,
createPlatform,
disposePlatform,
ComponentResolver,
ChangeDetectorRef
} from "angular2/core";
import {Console} from 'angular2/src/core/console';
import {BaseException} from 'angular2/src/facade/exceptions';
import {PromiseWrapper, PromiseCompleter, TimerWrapper} from "angular2/src/facade/async";
import {ListWrapper} from "angular2/src/facade/collection";
import {
ComponentFactory,
ComponentRef_,
ComponentRef
} from 'angular2/src/core/linker/component_factory';
import {ExceptionHandler} from 'angular2/src/facade/exception_handler';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
export function main() {
describe("ApplicationRef", () => {
it("should throw when reentering tick", () => {
var cdRef = <any>new SpyChangeDetectorRef();
var ref = new ApplicationRef_(null, null, null);
ref.registerChangeDetector(cdRef);
cdRef.spy("detectChanges").andCallFake(() => ref.tick());
expect(() => ref.tick()).toThrowError("ApplicationRef.tick is called recursively");
});
});
describe("bootstrap", () => {
var platform: PlatformRef;
var errorLogger: _ArrayLogger;
var someCompFactory: ComponentFactory;
describe("PlatformRef", () => {
var exceptionHandler =
new Provider(ExceptionHandler, {useValue: new ExceptionHandler(DOM, true)});
describe("asyncApplication", () => {
function expectProviders(injector: Injector, providers: Array<any>): void {
for (let i = 0; i < providers.length; i++) {
let provider = providers[i];
expect(injector.get(provider.token)).toBe(provider.useValue);
beforeEach(() => {
errorLogger = new _ArrayLogger();
disposePlatform();
platform = createPlatform(ReflectiveInjector.resolveAndCreate(PLATFORM_CORE_PROVIDERS));
someCompFactory =
new _MockComponentFactory(new _MockComponentRef(ReflectiveInjector.resolveAndCreate([])));
});
afterEach(() => { disposePlatform(); });
function createApplication(providers: any[]): ApplicationRef_ {
var appInjector = ReflectiveInjector.resolveAndCreate(
[
APPLICATION_CORE_PROVIDERS,
new Provider(Console, {useValue: new _MockConsole()}),
new Provider(ExceptionHandler, {useValue: new ExceptionHandler(errorLogger, false)}),
new Provider(ComponentResolver,
{useValue: new _MockComponentResolver(someCompFactory)}),
providers
],
platform.injector);
return appInjector.get(ApplicationRef);
}
describe("ApplicationRef", () => {
it("should throw when reentering tick", () => {
var cdRef = <any>new SpyChangeDetectorRef();
var ref = createApplication([]);
try {
ref.registerChangeDetector(cdRef);
cdRef.spy("detectChanges").andCallFake(() => ref.tick());
expect(() => ref.tick()).toThrowError("ApplicationRef.tick is called recursively");
} finally {
ref.unregisterChangeDetector(cdRef);
}
}
});
it("should merge syncronous and asyncronous providers",
describe('run', () => {
it('should rethrow errors even if the exceptionHandler is not rethrowing', () => {
var ref = createApplication([]);
expect(() => ref.run(() => { throw new BaseException('Test'); })).toThrowError('Test');
});
it('should return a promise with rejected errors even if the exceptionHandler is not rethrowing',
inject([AsyncTestCompleter, Injector], (async, injector) => {
var ref = createApplication([]);
var promise = ref.run(() => PromiseWrapper.reject('Test', null));
PromiseWrapper.catchError(promise, (e) => {
expect(e).toEqual('Test');
async.done();
});
}));
});
});
describe("coreLoadAndBootstrap", () => {
it("should wait for asynchronous app initializers",
inject([AsyncTestCompleter, Injector], (async, injector) => {
let ref = new PlatformRef_(injector, null);
let ASYNC_PROVIDERS = [new Provider(Foo, {useValue: new Foo()}), exceptionHandler];
let SYNC_PROVIDERS = [new Provider(Bar, {useValue: new Bar()})];
ref.asyncApplication((zone) => PromiseWrapper.resolve(ASYNC_PROVIDERS), SYNC_PROVIDERS)
.then((appRef) => {
var providers = ListWrapper.concat(ASYNC_PROVIDERS, SYNC_PROVIDERS);
expectProviders(appRef.injector, providers);
async.done();
});
}));
it("should allow function to be null",
inject([AsyncTestCompleter, Injector], (async, injector) => {
let ref = new PlatformRef_(injector, null);
let SYNC_PROVIDERS = [new Provider(Bar, {useValue: new Bar()}), exceptionHandler];
ref.asyncApplication(null, SYNC_PROVIDERS)
.then((appRef) => {
expectProviders(appRef.injector, SYNC_PROVIDERS);
async.done();
});
}));
function mockAsyncAppInitializer(completer: PromiseCompleter<any>,
providers: Array<any> = null, injector?: Injector) {
return () => {
if (providers != null) {
expectProviders(injector, providers);
}
TimerWrapper.setTimeout(() => completer.resolve(true), 1);
return completer.promise;
};
}
it("should wait for asyncronous app initializers",
inject([AsyncTestCompleter, Injector], (async, injector) => {
let ref = new PlatformRef_(injector, null);
let completer: PromiseCompleter<any> = PromiseWrapper.completer();
let SYNC_PROVIDERS = [
new Provider(Bar, {useValue: new Bar()}),
new Provider(APP_INITIALIZER,
{useValue: mockAsyncAppInitializer(completer), multi: true})
];
ref.asyncApplication(null, [SYNC_PROVIDERS, exceptionHandler])
.then((appRef) => {
expectProviders(appRef.injector,
SYNC_PROVIDERS.slice(0, SYNC_PROVIDERS.length - 1));
completer.promise.then((_) => async.done());
});
}));
it("should wait for async providers and then async app initializers",
inject([AsyncTestCompleter, Injector], (async, injector) => {
let ref = new PlatformRef_(injector, null);
let ASYNC_PROVIDERS = [new Provider(Foo, {useValue: new Foo()})];
let completer: PromiseCompleter<any> = PromiseWrapper.completer();
let SYNC_PROVIDERS = [
new Provider(Bar, {useValue: new Bar()}),
new Provider(APP_INITIALIZER,
{
useFactory: (injector) => mockAsyncAppInitializer(
<any>completer, ASYNC_PROVIDERS, injector),
multi: true,
deps: [Injector]
})
];
ref.asyncApplication((zone) => PromiseWrapper.resolve(ASYNC_PROVIDERS),
[SYNC_PROVIDERS, exceptionHandler])
.then((appRef) => {
expectProviders(appRef.injector,
SYNC_PROVIDERS.slice(0, SYNC_PROVIDERS.length - 1));
completer.promise.then((_) => async.done());
var initializerDone = false;
TimerWrapper.setTimeout(() => {
completer.resolve(true);
initializerDone = true;
}, 1);
var app = createApplication(
[new Provider(APP_INITIALIZER, {useValue: () => completer.promise, multi: true})]);
coreLoadAndBootstrap(app.injector, MyComp)
.then((compRef) => {
expect(initializerDone).toBe(true);
async.done();
});
}));
});
describe("application", () => {
it("should throw if an APP_INITIIALIZER returns a promise", inject([Injector], (injector) => {
let ref = new PlatformRef_(injector, null);
let appInitializer = new Provider(
APP_INITIALIZER, {useValue: () => PromiseWrapper.resolve([]), multi: true});
expect(() => ref.application([appInitializer, exceptionHandler]))
describe("coreBootstrap", () => {
it("should throw if an APP_INITIIALIZER is not yet resolved",
inject([Injector], (injector) => {
var app = createApplication([
new Provider(APP_INITIALIZER,
{useValue: () => PromiseWrapper.completer().promise, multi: true})
]);
expect(() => app.bootstrap(someCompFactory))
.toThrowError(
"Cannot use asyncronous app initializers with application. Use asyncApplication instead.");
"Cannot bootstrap as there are still asynchronous initializers running. Wait for them using waitForAsyncInitializers().");
}));
});
});
}
class Foo {
constructor() {}
@Component({selector: 'my-comp', template: ''})
class MyComp {
}
class Bar {
constructor() {}
class _ArrayLogger {
res: any[] = [];
log(s: any): void { this.res.push(s); }
logError(s: any): void { this.res.push(s); }
logGroup(s: any): void { this.res.push(s); }
logGroupEnd(){};
}
class _MockComponentFactory extends ComponentFactory {
constructor(private _compRef: ComponentRef) { super(null, null, null); }
create(injector: Injector, projectableNodes: any[][] = null,
rootSelectorOrNode: string | any = null): ComponentRef {
return this._compRef;
}
}
class _MockComponentResolver implements ComponentResolver {
constructor(private _compFactory: ComponentFactory) {}
resolveComponent(type: Type): Promise<ComponentFactory> {
return PromiseWrapper.resolve(this._compFactory);
}
clearCache() {}
}
class _MockComponentRef extends ComponentRef_ {
constructor(private _injector: Injector) { super(null, null); }
get injector(): Injector { return this._injector; }
get changeDetectorRef(): ChangeDetectorRef { return <any>new SpyChangeDetectorRef(); }
onDestroy(cb: Function) {}
}
class _MockConsole implements Console {
log(message) {}
}
@@ -18,7 +18,6 @@ import {
import {Predicate} from 'angular2/src/facade/collection';
import {Injector, OnDestroy, DebugElement, Type, ViewContainerRef, ViewChild} from 'angular2/core';
import {NgIf} from 'angular2/common';
import {Component, ViewMetadata} from 'angular2/src/core/metadata';
import {DynamicComponentLoader} from 'angular2/src/core/linker/dynamic_component_loader';
import {ElementRef} from 'angular2/src/core/linker/element_ref';
+5 -1
View File
@@ -8,7 +8,11 @@ import {DomAdapter} from 'angular2/src/platform/dom/dom_adapter';
import {SpyObject, proxy} from 'angular2/testing_internal';
export class SpyChangeDetectorRef extends SpyObject {
constructor() { super(ChangeDetectorRef); }
constructor() {
super(ChangeDetectorRef);
this.spy('detectChanges');
this.spy('checkNoChanges');
}
}
export class SpyIterableDifferFactory extends SpyObject {}
@@ -685,12 +685,12 @@ function commonTests() {
});
describe('exceptions', () => {
it('should call the on error callback when it is defined',
it('should call the on error callback when it is invoked via zone.runGuarded',
inject([AsyncTestCompleter], (async) => {
macroTask(() => {
var exception = new BaseException('sync');
_zone.run(() => { throw exception; });
_zone.runGuarded(() => { throw exception; });
expect(_errors.length).toBe(1);
expect(_errors[0]).toBe(exception);
@@ -698,6 +698,17 @@ function commonTests() {
});
}), testTimeout);
it('should not call the on error callback but rethrow when it is invoked via zone.run',
inject([AsyncTestCompleter], (async) => {
macroTask(() => {
var exception = new BaseException('sync');
expect(() => _zone.run(() => { throw exception; })).toThrowError('sync');
expect(_errors.length).toBe(0);
async.done();
});
}), testTimeout);
it('should call onError for errors from microtasks', inject([AsyncTestCompleter], (async) => {
var exception = new BaseException('async');
@@ -14,13 +14,22 @@ import {
} from 'angular2/testing_internal';
import {IS_DART, isPresent, stringify} from 'angular2/src/facade/lang';
import {bootstrap, BROWSER_PROVIDERS, BROWSER_APP_PROVIDERS} from 'angular2/platform/browser';
import {ApplicationRef} from 'angular2/src/core/application_ref';
import {ApplicationRef, PlatformRef} from 'angular2/src/core/application_ref';
import {Console} from 'angular2/src/core/console';
import {Component, Directive, OnDestroy, platform} from 'angular2/core';
import {Component, Directive, OnDestroy} from 'angular2/core';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {DOCUMENT} from 'angular2/src/platform/dom/dom_tokens';
import {PromiseWrapper, TimerWrapper} from 'angular2/src/facade/async';
import {provide, Inject, Injector, PLATFORM_INITIALIZER, APP_INITIALIZER} from 'angular2/core';
import {
provide,
Inject,
Injector,
PLATFORM_INITIALIZER,
APP_INITIALIZER,
coreLoadAndBootstrap,
createPlatform,
ReflectiveInjector
} from 'angular2/core';
import {disposePlatform} from 'angular2/src/core/application_ref';
import {ExceptionHandler, BaseException} from 'angular2/src/facade/exceptions';
import {Testability, TestabilityRegistry} from 'angular2/src/core/testability/testability';
@@ -91,6 +100,8 @@ export function main() {
describe('bootstrap factory method', () => {
beforeEach(() => {
disposePlatform();
fakeDoc = DOM.createHtmlDocument();
el = DOM.createElement('hello-app', fakeDoc);
el2 = DOM.createElement('hello-app-2', fakeDoc);
@@ -105,21 +116,16 @@ export function main() {
afterEach(disposePlatform);
it('should throw if bootstrapped Directive is not a Component',
inject([AsyncTestCompleter], (async) => {
var logger = new _ArrayLogger();
var exceptionHandler = new ExceptionHandler(logger, false);
var refPromise =
bootstrap(HelloRootDirectiveIsNotCmp,
[testProviders, provide(ExceptionHandler, {useValue: exceptionHandler})]);
PromiseWrapper.then(refPromise, null, (exception) => {
expect(exception).toContainError(
`Could not compile '${stringify(HelloRootDirectiveIsNotCmp)}' because it is not a component.`);
expect(logger.res.join("")).toContain("Could not compile");
async.done();
});
}));
it('should throw if bootstrapped Directive is not a Component', () => {
var logger = new _ArrayLogger();
var exceptionHandler = new ExceptionHandler(logger, false);
expect(
() => bootstrap(HelloRootDirectiveIsNotCmp,
[testProviders, provide(ExceptionHandler, {useValue: exceptionHandler})]))
.toThrowError(
`Could not compile '${stringify(HelloRootDirectiveIsNotCmp)}' because it is not a component.`);
expect(logger.res.join("")).toContain("Could not compile");
});
it('should throw if no element is found', inject([AsyncTestCompleter], (async) => {
var logger = new _ArrayLogger();
@@ -201,8 +207,11 @@ export function main() {
it('should unregister change detectors when components are disposed',
inject([AsyncTestCompleter], (async) => {
var app = platform(BROWSER_PROVIDERS).application([BROWSER_APP_PROVIDERS, testProviders]);
app.bootstrap(HelloRootCmp)
var platform = createPlatform(ReflectiveInjector.resolveAndCreate(BROWSER_PROVIDERS));
var app = ReflectiveInjector.resolveAndCreate([BROWSER_APP_PROVIDERS, testProviders],
platform.injector)
.get(ApplicationRef);
coreLoadAndBootstrap(app.injector, HelloRootCmp)
.then((ref) => {
ref.destroy();
expect(() => app.tick()).not.toThrow();
@@ -232,18 +241,21 @@ export function main() {
}));
it("should run platform initializers", inject([Log], (log: Log) => {
let p = platform([
let p = createPlatform(ReflectiveInjector.resolveAndCreate([
BROWSER_PROVIDERS,
provide(PLATFORM_INITIALIZER, {useValue: log.fn("platform_init1"), multi: true}),
provide(PLATFORM_INITIALIZER, {useValue: log.fn("platform_init2"), multi: true})
]);
]));
expect(log.result()).toEqual("platform_init1; platform_init2");
log.clear();
p.application([
BROWSER_APP_PROVIDERS,
provide(APP_INITIALIZER, {useValue: log.fn("app_init1"), multi: true}),
provide(APP_INITIALIZER, {useValue: log.fn("app_init2"), multi: true})
]);
var a = ReflectiveInjector.resolveAndCreate(
[
BROWSER_APP_PROVIDERS,
provide(APP_INITIALIZER, {useValue: log.fn("app_init1"), multi: true}),
provide(APP_INITIALIZER, {useValue: log.fn("app_init2"), multi: true})
],
p.injector);
a.get(ApplicationRef);
expect(log.result()).toEqual("app_init1; app_init2");
}));
+7 -2
View File
@@ -140,7 +140,6 @@ var NG_COMPILER = [
];
var NG_CORE = [
'APP_COMPONENT',
'APP_INITIALIZER',
'APP_ID',
'AngularEntrypoint:dart',
@@ -261,7 +260,12 @@ var NG_CORE = [
'provide',
'createNgZone',
'forwardRef:js',
'platform',
'coreBootstrap',
'coreLoadAndBootstrap',
'createPlatform',
'disposePlatform',
'getPlatform',
'assertPlatform',
'resolveForwardRef:js',
'PLATFORM_COMMON_PROVIDERS',
'PLATFORM_INITIALIZER',
@@ -306,6 +310,7 @@ var NG_PLATFORM_BROWSER = [
'ELEMENT_PROBE_PROVIDERS_PROD_MODE',
'Title',
'bootstrap',
'browserPlatform',
'disableDebugTools',
'enableDebugTools',
'inspectNativeElement'
@@ -54,7 +54,7 @@ export function main() {
]);
// do not refactor out the `bootstrap` functionality. We still want to
// keep this test around so we can ensure that bootstrapping a router works
// keep this test around so we can ensure that bootstrap a router works
it('should bootstrap a simple app', inject([AsyncTestCompleter], (async) => {
var fakeDoc = DOM.createHtmlDocument();
var el = DOM.createElement('app-cmp', fakeDoc);
@@ -153,7 +153,8 @@ export class DynamicLoaderCmp {
this._componentRef.destroy();
this._componentRef = null;
}
return this._dynamicComponentLoader.loadNextToLocation(DynamicallyLoadedComponent, viewport)
return this._dynamicComponentLoader.loadNextToLocation(DynamicallyLoadedComponent,
this.viewport)
.then((cmp) => { this._componentRef = cmp; });
}
}
@@ -19,8 +19,11 @@ main() {
it("should be able to load in a Dart VM", () {
reflector.reflectionCapabilities = new ReflectionCapabilities();
var buses = createPairedMessageBuses();
platform([WORKER_APP_PLATFORM])
.application([WORKER_APP_APPLICATION_COMMON]);
disposePlatform();
var platform = createPlatform(ReflectiveInjector.resolveAndCreate(WORKER_APP_PLATFORM));
var appInjector = ReflectiveInjector.resolveAndCreate(WORKER_APP_APPLICATION_COMMON,
platform.injector);
appInjector.get(ApplicationRef);
});
});
}