Permalink
Browse files

feat(ivy): add support of ApplicationRef.bootstrapModuleFactory (#23811)

PR Close #23811
  • Loading branch information...
jasonaden authored and mhevery committed May 9, 2018
1 parent 7de2ba0 commit e3759f7a7361991e2e3fd4de67ab139babd7c166
Showing with 589 additions and 156 deletions.
  1. +29 −16 packages/core/src/application_module.ts
  2. +2 −0 packages/core/src/core_render3_private_export.ts
  3. +1 −1 packages/core/src/di.ts
  4. +6 −3 packages/core/src/di/injector.ts
  5. +2 −6 packages/core/src/di/provider.ts
  6. +17 −12 packages/core/src/di/r3_injector.ts
  7. +2 −2 packages/core/src/render3/assert.ts
  8. +14 −35 packages/core/src/render3/component.ts
  9. +176 −0 packages/core/src/render3/component_ref.ts
  10. +4 −4 packages/core/src/render3/di.ts
  11. +4 −3 packages/core/src/render3/index.ts
  12. +37 −28 packages/core/src/render3/instructions.ts
  13. +2 −2 packages/core/src/render3/interfaces/view.ts
  14. +73 −0 packages/core/src/render3/ng_module_ref.ts
  15. +3 −3 packages/core/src/render3/node_assert.ts
  16. +1 −1 packages/core/src/render3/node_manipulation.ts
  17. +2 −2 packages/core/src/render3/node_selector_matcher.ts
  18. +4 −4 packages/core/src/render3/query.ts
  19. +16 −2 packages/core/src/render3/view_ref.ts
  20. +95 −0 packages/core/test/application_ref_integration_spec.ts
  21. +3 −0 packages/core/test/bundling/hello_world/bundle.golden_symbols.json
  22. +1 −1 packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json
  23. +4 −1 packages/core/test/bundling/todo/bundle.golden_symbols.json
  24. +20 −0 packages/core/test/di/r3_injector_spec.ts
  25. +25 −2 packages/core/test/render3/component_spec.ts
  26. +1 −1 packages/core/test/render3/render_util.ts
  27. +37 −24 packages/platform-browser/src/browser.ts
  28. +7 −2 tools/public_api_guard/core/core.d.ts
  29. +1 −1 tools/public_api_guard/platform-browser/platform-browser.d.ts
@@ -10,11 +10,15 @@ import {APP_INITIALIZER, ApplicationInitStatus} from './application_init';
import {ApplicationRef} from './application_ref';
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
import {forwardRef} from './di/forward_ref';
import {Console} from './console';
import {InjectionToken, Injector, StaticProvider} from './di';
import {Inject, Optional, SkipSelf} from './di/metadata';
import {ErrorHandler} from './error_handler';
import {LOCALE_ID} from './i18n/tokens';
import {ComponentFactoryResolver} from './linker';
import {Compiler} from './linker/compiler';
import {NgModule} from './metadata';
import {NgZone} from './zone';
export function _iterableDiffersFactory() {
return defaultIterableDiffers;
@@ -28,27 +32,36 @@ export function _localeFactory(locale?: string): string {
return locale || 'en-US';
}
export const APPLICATION_MODULE_PROVIDERS: StaticProvider[] = [
{
provide: ApplicationRef,
useClass: ApplicationRef,
deps:
[NgZone, Console, Injector, ErrorHandler, ComponentFactoryResolver, ApplicationInitStatus]
},
{
provide: ApplicationInitStatus,
useClass: ApplicationInitStatus,
deps: [[new Optional(), APP_INITIALIZER]]
},
{provide: Compiler, useClass: Compiler, deps: []},
APP_ID_RANDOM_PROVIDER,
{provide: IterableDiffers, useFactory: _iterableDiffersFactory, deps: []},
{provide: KeyValueDiffers, useFactory: _keyValueDiffersFactory, deps: []},
{
provide: LOCALE_ID,
useFactory: _localeFactory,
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
},
];
/**
* This module includes the providers of @angular/core that are needed
* to bootstrap components via `ApplicationRef`.
*
* @experimental
*/
@NgModule({
providers: [
ApplicationRef,
ApplicationInitStatus,
Compiler,
APP_ID_RANDOM_PROVIDER,
{provide: IterableDiffers, useFactory: _iterableDiffersFactory},
{provide: KeyValueDiffers, useFactory: _keyValueDiffersFactory},
{
provide: LOCALE_ID,
useFactory: _localeFactory,
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
},
],
})
@NgModule({providers: APPLICATION_MODULE_PROVIDERS})
export class ApplicationModule {
// Inject ApplicationRef to make it eager...
constructor(appRef: ApplicationRef) {}
@@ -23,6 +23,8 @@ export {
injectAttribute as ɵinjectAttribute,
PublicFeature as ɵPublicFeature,
NgOnChangesFeature as ɵNgOnChangesFeature,
NgModuleDef as ɵNgModuleDef,
NgModuleType as ɵNgModuleType,
CssSelectorList as ɵCssSelectorList,
markDirty as ɵmarkDirty,
NC as ɵNC,
@@ -18,7 +18,7 @@ export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
export {Injectable, InjectableDecorator, InjectableProvider} from './di/injectable';
export {inject, InjectFlags, INJECTOR, Injector} from './di/injector';
export {ReflectiveInjector} from './di/reflective_injector';
export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider';
export {StaticProvider, ValueProvider, ConstructorSansProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider';
export {createInjector} from './di/r3_injector';
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
export {ReflectiveKey} from './di/reflective_key';
@@ -32,6 +32,9 @@ export const INJECTOR = new InjectionToken<Injector>('INJECTOR');
export class NullInjector implements Injector {
get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
if (notFoundValue === _THROW_IF_NOT_FOUND) {
// Intentionally left behind: With dev tools open the debugger will stop here. There is no
// reason why correctly written application should cause this exception.
debugger;
throw new Error(`NullInjectorError: No provider for ${stringify(token)}!`);
}
return notFoundValue;
@@ -487,11 +490,11 @@ export function injectArgs(types: (Type<any>| InjectionToken<any>| any[])[]): an
for (let j = 0; j < arg.length; j++) {
const meta = arg[j];
if (meta instanceof Optional || meta.__proto__.ngMetadataName === 'Optional') {
if (meta instanceof Optional || meta.ngMetadataName === 'Optional') {
flags |= InjectFlags.Optional;
} else if (meta instanceof SkipSelf || meta.__proto__.ngMetadataName === 'SkipSelf') {
} else if (meta instanceof SkipSelf || meta.ngMetadataName === 'SkipSelf') {
flags |= InjectFlags.SkipSelf;
} else if (meta instanceof Self || meta.__proto__.ngMetadataName === 'Self') {
} else if (meta instanceof Self || meta.ngMetadataName === 'Self') {
flags |= InjectFlags.Self;
} else if (meta instanceof Inject) {
type = meta.token;
@@ -151,10 +151,6 @@ export interface StaticClassProvider extends StaticClassSansProvider {
*
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/provider_spec.ts region='ConstructorSansProvider'}
*
* @experimental
*/
export interface ConstructorSansProvider {
@@ -453,5 +449,5 @@ export interface ClassProvider extends ClassSansProvider {
*
*
*/
export type Provider =
TypeProvider | ValueProvider | ClassProvider | ExistingProvider | FactoryProvider | any[];
export type Provider = TypeProvider | ValueProvider | ClassProvider | ConstructorProvider |
ExistingProvider | FactoryProvider | any[];
@@ -14,7 +14,7 @@ import {InjectableDef, InjectableType, InjectorDef, InjectorType, InjectorTypeWi
import {resolveForwardRef} from './forward_ref';
import {InjectableDefToken, InjectionToken} from './injection_token';
import {INJECTOR, InjectFlags, Injector, NullInjector, THROW_IF_NOT_FOUND, USE_VALUE, inject, injectArgs, setCurrentInjector} from './injector';
import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, Provider, StaticClassProvider, TypeProvider, ValueProvider} from './provider';
import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, Provider, StaticClassProvider, StaticProvider, TypeProvider, ValueProvider} from './provider';
import {APP_ROOT} from './scope';
@@ -64,14 +64,15 @@ interface Record<T> {
}
/**
* Create a new `Injector` which is configured using `InjectorType`s.
* Create a new `Injector` which is configured using a `defType` of `InjectorType<any>`s.
*
* @experimental
*/
export function createInjector(
defType: /* InjectorType<any> */ any, parent: Injector | null = null): Injector {
defType: /* InjectorType<any> */ any, parent: Injector | null = null,
additionalProviders: StaticProvider[] | null = null): Injector {
parent = parent || getNullInjector();
return new R3Injector(defType, parent);
return new R3Injector(defType, additionalProviders, parent);
}
export class R3Injector {
@@ -101,12 +102,18 @@ export class R3Injector {
*/
private destroyed = false;
constructor(def: InjectorType<any>, readonly parent: Injector) {
constructor(
def: InjectorType<any>, additionalProviders: StaticProvider[]|null,
readonly parent: Injector) {
// Start off by creating Records for every provider declared in every InjectorType
// included transitively in `def`.
deepForEach(
[def], injectorDef => this.processInjectorType(injectorDef, new Set<InjectorType<any>>()));
additionalProviders &&
deepForEach(additionalProviders, provider => this.processProvider(provider));
// Make sure the INJECTOR token provides this injector.
this.records.set(INJECTOR, makeRecord(undefined, this));
@@ -284,20 +291,18 @@ export class R3Injector {
throw new Error(`Mixed multi-provider for ${token}.`);
}
} else {
token = provider;
multiRecord = makeRecord(undefined, NOT_YET, true);
multiRecord.factory = () => injectArgs(multiRecord !.multi !);
this.records.set(token, multiRecord);
}
token = provider;
multiRecord.multi !.push(provider);
} else {
const existing = this.records.get(token);
if (existing && existing.multi !== undefined) {
throw new Error(`Mixed multi-provider for ${stringify(token)}`);
}
}
const existing = this.records.get(token);
if (existing && existing.multi !== undefined) {
throw new Error(`Mixed multi-provider for ${token}`);
}
this.records.set(token, record);
}
@@ -46,13 +46,13 @@ export function assertGreaterThan<T>(actual: T, expected: T, msg: string) {
}
}
export function assertNull<T>(actual: T, msg: string) {
export function assertNotDefined<T>(actual: T, msg: string) {
if (actual != null) {
throwError(msg);
}
}
export function assertNotNull<T>(actual: T, msg: string) {
export function assertDefined<T>(actual: T, msg: string) {
if (actual == null) {
throwError(msg);
}
@@ -13,7 +13,7 @@ import {Injector} from '../di/injector';
import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
import {Sanitizer} from '../sanitization/security';
import {assertComponentType, assertNotNull} from './assert';
import {assertComponentType, assertDefined} from './assert';
import {queueInitHooks, queueLifecycleHooks} from './hooks';
import {CLEAN_PROMISE, ROOT_DIRECTIVE_INDICES, _getComponentHostLElementNode, baseDirectiveCreate, createLView, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getRootView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, setHostBindings} from './instructions';
import {ComponentDef, ComponentType} from './interfaces/definition';
@@ -72,31 +72,6 @@ export interface CreateComponentOptions {
}
/**
* Bootstraps a component, then creates and returns a `ComponentRef` for that component.
*
* @param componentType Component to bootstrap
* @param options Optional parameters which control bootstrapping
*/
export function createComponentRef<T>(
componentType: ComponentType<T>, opts: CreateComponentOptions): viewEngine_ComponentRef<T> {
const component = renderComponent(componentType, opts);
const hostView = _getComponentHostLElementNode(component).data as LView;
const hostViewRef = new ViewRef(hostView, component);
return {
location: {nativeElement: getHostElement(component)},
injector: opts.injector || NULL_INJECTOR,
instance: component,
hostView: hostViewRef,
changeDetectorRef: hostViewRef,
componentType: componentType,
// TODO: implement destroy and onDestroy
destroy: () => {},
onDestroy: (cb: Function) => {}
};
}
// TODO: A hack to not pull in the NullInjector from @angular/core.
export const NULL_INJECTOR: Injector = {
get: (token: any, notFoundValue?: any) => {
@@ -131,12 +106,8 @@ export function renderComponent<T>(
// The first index of the first selector is the tag name.
const componentTag = componentDef.selectors ![0] ![0] as string;
const hostNode = locateHostElement(rendererFactory, opts.host || componentTag);
const rootContext: RootContext = {
// Incomplete initialization due to circular reference.
component: null !,
scheduler: opts.scheduler || requestAnimationFrame.bind(window),
clean: CLEAN_PROMISE,
};
const rootContext = createRootContext(opts.scheduler || requestAnimationFrame.bind(window));
const rootView: LView = createLView(
rendererFactory.createRenderer(hostNode, componentDef.rendererType),
createTView(-1, null, null, null), rootContext,
@@ -152,8 +123,8 @@ export function renderComponent<T>(
elementNode = hostElement(componentTag, hostNode, componentDef, sanitizer);
// Create directive instance with factory() and store at index 0 in directives array
component = rootContext.component =
baseDirectiveCreate(0, componentDef.factory(), componentDef) as T;
rootContext.components.push(
component = baseDirectiveCreate(0, componentDef.factory(), componentDef) as T);
initChangeDetectorIfExisting(elementNode.nodeInjector, component, elementNode.data !);
opts.hostFeatures && opts.hostFeatures.forEach((feature) => feature(component, componentDef));
@@ -169,6 +140,14 @@ export function renderComponent<T>(
return component;
}
export function createRootContext(scheduler: (workFn: () => void) => void): RootContext {
return {
components: [],
scheduler: scheduler,
clean: CLEAN_PROMISE,
};
}
/**
* Used to enable lifecycle hooks on the root component.
*
@@ -198,7 +177,7 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): v
*/
function getRootContext(component: any): RootContext {
const rootContext = getRootView(component).context as RootContext;
ngDevMode && assertNotNull(rootContext, 'rootContext');
ngDevMode && assertDefined(rootContext, 'rootContext');
return rootContext;
}
Oops, something went wrong.

0 comments on commit e3759f7

Please sign in to comment.