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

PR Close #23811
This commit is contained in:
Jason Aden 2018-05-09 16:49:39 -07:00 committed by Miško Hevery
parent 7de2ba0e22
commit e3759f7a73
29 changed files with 589 additions and 156 deletions

View File

@ -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) {}

View File

@ -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,

View File

@ -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';

View File

@ -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;

View File

@ -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[];

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -0,0 +1,176 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ChangeDetectorRef} from '../change_detection/change_detector_ref';
import {InjectionToken} from '../di/injection_token';
import {Injector, inject} from '../di/injector';
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
import {ElementRef} from '../linker/element_ref';
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
import {RendererFactory2} from '../render/api';
import {Type} from '../type';
import {assertComponentType, assertDefined} from './assert';
import {createRootContext} from './component';
import {baseDirectiveCreate, createLView, createTView, enterView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement} from './instructions';
import {ComponentDef, ComponentType} from './interfaces/definition';
import {LElementNode} from './interfaces/node';
import {RElement} from './interfaces/renderer';
import {LView, LViewFlags, RootContext} from './interfaces/view';
import {ViewRef} from './view_ref';
export class ComponentFactoryResolver extends viewEngine_ComponentFactoryResolver {
resolveComponentFactory<T>(component: Type<T>): viewEngine_ComponentFactory<T> {
ngDevMode && assertComponentType(component);
const componentDef = (component as ComponentType<T>).ngComponentDef;
return new ComponentFactory(componentDef);
}
}
function toRefArray(map: {[key: string]: string}): {propName: string; templateName: string;}[] {
const array: {propName: string; templateName: string;}[] = [];
for (let nonMinified in map) {
if (map.hasOwnProperty(nonMinified)) {
const minified = map[nonMinified];
array.push({propName: minified, templateName: nonMinified});
}
}
return array;
}
/**
* Default {@link RootContext} for all components rendered with {@link renderComponent}.
*/
export const ROOT_CONTEXT = new InjectionToken<RootContext>(
'ROOT_CONTEXT_TOKEN',
{providedIn: 'root', factory: () => createRootContext(inject(SCHEDULER))});
/**
* A change detection scheduler token for {@link RootContext}. This token is the default value used
* for the default `RootContext` found in the {@link ROOT_CONTEXT} token.
*/
export const SCHEDULER = new InjectionToken<((fn: () => void) => void)>(
'SCHEDULER_TOKEN', {providedIn: 'root', factory: () => requestAnimationFrame.bind(window)});
/**
* Render3 implementation of {@link viewEngine_ComponentFactory}.
*/
export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
selector: string;
componentType: Type<any>;
ngContentSelectors: string[];
get inputs(): {propName: string; templateName: string;}[] {
return toRefArray(this.componentDef.inputs);
}
get outputs(): {propName: string; templateName: string;}[] {
return toRefArray(this.componentDef.outputs);
}
constructor(private componentDef: ComponentDef<any>) {
super();
this.componentType = componentDef.type;
this.selector = componentDef.selectors[0][0] as string;
this.ngContentSelectors = [];
}
create(
parentComponentInjector: Injector, projectableNodes?: any[][]|undefined,
rootSelectorOrNode?: any,
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<T> {
ngDevMode && assertDefined(ngModule, 'ngModule should always be defined');
const rendererFactory = ngModule ? ngModule.injector.get(RendererFactory2) : document;
const hostNode = locateHostElement(rendererFactory, rootSelectorOrNode);
// The first index of the first selector is the tag name.
const componentTag = this.componentDef.selectors ![0] ![0] as string;
const rootContext: RootContext = ngModule !.injector.get(ROOT_CONTEXT);
// Create the root view. Uses empty TView and ContentTemplate.
const rootView: LView = createLView(
rendererFactory.createRenderer(hostNode, this.componentDef.rendererType),
createTView(-1, null, null, null), null,
this.componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
rootView.injector = ngModule && ngModule.injector || null;
// rootView is the parent when bootstrapping
const oldView = enterView(rootView, null !);
let component: T;
let elementNode: LElementNode;
try {
if (rendererFactory.begin) rendererFactory.begin();
// Create element node at index 0 in data array
elementNode = hostElement(componentTag, hostNode, this.componentDef);
// Create directive instance with factory() and store at index 0 in directives array
rootContext.components.push(
component = baseDirectiveCreate(0, this.componentDef.factory(), this.componentDef) as T);
initChangeDetectorIfExisting(elementNode.nodeInjector, component, elementNode.data !);
} finally {
enterView(oldView, null);
if (rendererFactory.end) rendererFactory.end();
}
// TODO(misko): this is the wrong injector here.
return new ComponentRef(
this.componentType, component, rootView, ngModule !.injector, hostNode !);
}
}
/**
* Represents an instance of a Component created via a {@link ComponentFactory}.
*
* `ComponentRef` provides access to the Component Instance as well other objects related to this
* Component Instance and allows you to destroy the Component Instance via the {@link #destroy}
* method.
*
*/
export class ComponentRef<T> extends viewEngine_ComponentRef<T> {
destroyCbs: (() => void)[]|null = [];
location: ElementRef<any>;
injector: Injector;
instance: T;
hostView: ViewRef<T>;
changeDetectorRef: ChangeDetectorRef;
componentType: Type<T>;
constructor(
componentType: Type<T>, instance: T, rootView: LView, injector: Injector,
hostNode: RElement) {
super();
this.instance = instance;
/* TODO(jasonaden): This is incomplete, to be adjusted in follow-up PR. Notes from Kara:When
* ViewRef.detectChanges is called from ApplicationRef.tick, it will call detectChanges at the
* component instance level. I suspect this means that lifecycle hooks and host bindings on the
* given component won't work (as these are always called at the level above a component).
*
* In render2, ViewRef.detectChanges uses the root view instance for view checks, not the
* component instance. So passing in the root view (1 level above the component) is sufficient.
* We might want to think about creating a fake component for the top level? Or overwrite
* detectChanges with a function that calls tickRootContext? */
this.hostView = this.changeDetectorRef = new ViewRef(rootView, instance);
this.injector = injector;
this.location = new ElementRef(hostNode);
this.componentType = componentType;
}
destroy(): void {
ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed');
this.destroyCbs !.forEach(fn => fn());
this.destroyCbs = null;
}
onDestroy(callback: () => void): void {
ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed');
this.destroyCbs !.push(callback);
}
}

View File

@ -18,9 +18,9 @@ import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_co
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref';
import {Type} from '../type';
import {assertGreaterThan, assertLessThan, assertNotNull} from './assert';
import {assertDefined, assertGreaterThan, assertLessThan} from './assert';
import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTNode, createTView, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions';
import {ComponentTemplate, DirectiveDef} from './interfaces/definition';
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList} from './interfaces/definition';
import {LInjector} from './interfaces/injector';
import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node';
import {LQueries, QueryReadType} from './interfaces/query';
@ -256,7 +256,7 @@ export function injectAttribute(attrNameToInject: string): string|undefined {
const lElement = getPreviousOrParentNode() as LElementNode;
ngDevMode && assertNodeType(lElement, TNodeType.Element);
const tElement = lElement.tNode;
ngDevMode && assertNotNull(tElement, 'expecting tNode');
ngDevMode && assertDefined(tElement, 'expecting tNode');
const attrs = tElement.attrs;
if (attrs) {
for (let i = 0; i < attrs.length; i = i + 2) {
@ -707,7 +707,7 @@ export function getOrCreateTemplateRef<T>(di: LInjector): viewEngine_TemplateRef
hostTNode.tViews = createTView(
-1, hostNode.data.template !, hostTView.directiveRegistry, hostTView.pipeRegistry);
}
ngDevMode && assertNotNull(hostTNode.tViews, 'TView must be allocated');
ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated');
di.templateRef = new TemplateRef<any>(
getOrCreateElementRef(di), hostTNode.tViews as TView, getRenderer(), hostNode.data.queries);
}

View File

@ -6,16 +6,16 @@
* found in the LICENSE file at https://angular.io/license
*/
import {LifecycleHooksFeature, createComponentRef, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
import {LifecycleHooksFeature, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, definePipe} from './definition';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType, PipeDef} from './interfaces/definition';
export {ComponentFactory, ComponentFactoryResolver, ComponentRef} from './component_ref';
export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, injectAttribute, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
export {RenderFlags} from './interfaces/definition';
export {CssSelectorList} from './interfaces/projection';
// Naming scheme:
// - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View),
// C(Container), L(Listener)
@ -73,6 +73,8 @@ export {
tick,
} from './instructions';
export {NgModuleDef, NgModuleFactory, NgModuleRef, NgModuleType} from './ng_module_ref';
export {
AttributeMarker
} from './interfaces/node';
@ -122,7 +124,6 @@ export {
defineComponent,
defineDirective,
definePipe,
createComponentRef,
getHostElement,
getRenderedText,
renderComponent,

View File

@ -8,7 +8,11 @@
import './ng_dev_mode';
import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, assertSame} from './assert';
import {Sanitizer} from '../sanitization/security';
import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotEqual, assertSame} from './assert';
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
import {LContainer} from './interfaces/container';
import {LInjector} from './interfaces/injector';
import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
@ -22,10 +26,7 @@ import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {isDifferent, stringify} from './util';
import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
import {ViewRef} from './view_ref';
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
import {Sanitizer} from '../sanitization/security';
/**
* Directive (D) sets a property on all component instances using this constant as a key and the
@ -406,7 +407,8 @@ export function createLNode(
if ((type & TNodeType.ViewOrElement) === TNodeType.ViewOrElement && isState) {
// Bit of a hack to bust through the readonly because there is a circular dep between
// LView and LNode.
ngDevMode && assertNull((state as LView).node, 'LView.node should not have been initialized');
ngDevMode &&
assertNotDefined((state as LView).node, 'LView.node should not have been initialized');
(state as{node: LNode}).node = node;
if (firstTemplatePass) (state as LView).tView.node = node.tNode;
}
@ -455,7 +457,7 @@ export function renderTemplate<T>(
sanitizer));
}
const hostView = host.data !;
ngDevMode && assertNotNull(hostView, 'Host node should have an LView defined in host.data.');
ngDevMode && assertDefined(hostView, 'Host node should have an LView defined in host.data.');
renderComponentOrTemplate(host, hostView, context, template);
return host;
}
@ -1300,8 +1302,8 @@ export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
if (value !== NO_CHANGE) {
ngDevMode && assertDataInRange(index);
const existingNode = data[index] as LTextNode;
ngDevMode && assertNotNull(existingNode, 'LNode should exist');
ngDevMode && assertNotNull(existingNode.native, 'native element should exist');
ngDevMode && assertDefined(existingNode, 'LNode should exist');
ngDevMode && assertDefined(existingNode.native, 'native element should exist');
ngDevMode && ngDevMode.rendererSetText++;
isProceduralRenderer(renderer) ? renderer.setValue(existingNode.native, stringify(value)) :
existingNode.native.textContent = stringify(value);
@ -1325,7 +1327,7 @@ export function directiveCreate<T>(
index: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<T>): T {
const instance = baseDirectiveCreate(index, directive, directiveDef);
ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
ngDevMode && assertDefined(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
const tNode = previousOrParentNode.tNode;
const isComponent = (directiveDef as ComponentDef<T>).template;
@ -1494,7 +1496,7 @@ function generateInitialInputs(
export function createLContainer(
parentLNode: LNode, currentView: LView, template?: ComponentTemplate<any>,
isForViewContainerRef?: boolean): LContainer {
ngDevMode && assertNotNull(parentLNode, 'containers should have a parent');
ngDevMode && assertDefined(parentLNode, 'containers should have a parent');
return <LContainer>{
views: [],
nextIndex: isForViewContainerRef ? null : 0,
@ -1611,7 +1613,7 @@ function refreshDynamicChildren() {
const lViewNode = container.views[i];
// The directives and pipes are not needed here as an existing view is only being refreshed.
const dynamicView = lViewNode.data;
ngDevMode && assertNotNull(dynamicView.tView, 'TView must be allocated');
ngDevMode && assertDefined(dynamicView.tView, 'TView must be allocated');
renderEmbeddedTemplate(lViewNode, dynamicView.tView, dynamicView.context !, renderer);
}
}
@ -1699,7 +1701,7 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags {
function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TView {
ngDevMode && assertNodeType(parent, TNodeType.Container);
const containerTViews = (parent !.tNode as TContainerNode).tViews as TView[];
ngDevMode && assertNotNull(containerTViews, 'TView expected');
ngDevMode && assertDefined(containerTViews, 'TView expected');
ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array');
if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) {
const tView = currentView.tView;
@ -1773,7 +1775,7 @@ export function componentRefresh<T>(directiveIndex: number, elementIndex: number
ngDevMode && assertDataInRange(elementIndex);
const element = data ![elementIndex] as LElementNode;
ngDevMode && assertNodeType(element, TNodeType.Element);
ngDevMode && assertNotNull(element.data, `Component's host node should have an LView attached.`);
ngDevMode && assertDefined(element.data, `Component's host node should have an LView attached.`);
const hostView = element.data !;
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
@ -1928,13 +1930,13 @@ export function projection(
function findComponentHost(lView: LView): LElementNode {
let viewRootLNode = lView.node;
while (viewRootLNode.tNode.type === TNodeType.View) {
ngDevMode && assertNotNull(lView.parent, 'lView.parent');
ngDevMode && assertDefined(lView.parent, 'lView.parent');
lView = lView.parent !;
viewRootLNode = lView.node;
}
ngDevMode && assertNodeType(viewRootLNode, TNodeType.Element);
ngDevMode && assertNotNull(viewRootLNode.data, 'node.data');
ngDevMode && assertDefined(viewRootLNode.data, 'node.data');
return viewRootLNode as LElementNode;
}
@ -2012,7 +2014,7 @@ export function markViewDirty(view: LView): void {
}
currentView.flags |= LViewFlags.Dirty;
ngDevMode && assertNotNull(currentView !.context, 'rootContext');
ngDevMode && assertDefined(currentView !.context, 'rootContext');
scheduleTick(currentView !.context as RootContext);
}
@ -2033,7 +2035,7 @@ export function scheduleTick<T>(rootContext: RootContext) {
let res: null|((val: null) => void);
rootContext.clean = new Promise<null>((r) => res = r);
rootContext.scheduler(() => {
tick(rootContext.component);
tickRootContext(rootContext);
res !(null);
rootContext.clean = _CLEAN_PROMISE;
});
@ -2054,11 +2056,18 @@ export function scheduleTick<T>(rootContext: RootContext) {
*/
export function tick<T>(component: T): void {
const rootView = getRootView(component);
const rootComponent = (rootView.context as RootContext).component;
const hostNode = _getComponentHostLElementNode(rootComponent);
const rootContext = rootView.context as RootContext;
tickRootContext(rootContext);
}
ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView');
renderComponentOrTemplate(hostNode, rootView, rootComponent);
function tickRootContext(rootContext: RootContext) {
for (let i = 0; i < rootContext.components.length; i++) {
const rootComponent = rootContext.components[i];
const hostNode = _getComponentHostLElementNode(rootComponent);
ngDevMode && assertDefined(hostNode.data, 'Component host node should be attached to an LView');
renderComponentOrTemplate(hostNode, getRootView(rootComponent), rootComponent);
}
}
/**
@ -2069,7 +2078,7 @@ export function tick<T>(component: T): void {
*/
export function getRootView(component: any): LView {
ngDevMode && assertNotNull(component, 'component');
ngDevMode && assertDefined(component, 'component');
const lElementNode = _getComponentHostLElementNode(component);
let lView = lElementNode.view;
while (lView.parent) {
@ -2093,7 +2102,7 @@ export function getRootView(component: any): LView {
*/
export function detectChanges<T>(component: T): void {
const hostNode = _getComponentHostLElementNode(component);
ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView');
ngDevMode && assertDefined(hostNode.data, 'Component host node should be attached to an LView');
detectChangesInternal(hostNode.data as LView, hostNode, component);
}
@ -2142,7 +2151,7 @@ export function detectChangesInternal<T>(hostView: LView, hostNode: LElementNode
* @param component Component to mark as dirty.
*/
export function markDirty<T>(component: T) {
ngDevMode && assertNotNull(component, 'component');
ngDevMode && assertDefined(component, 'component');
const lElementNode = _getComponentHostLElementNode(component);
markViewDirty(lElementNode.view);
}
@ -2388,7 +2397,7 @@ export function load<T>(index: number): T {
/** Retrieves a value from the `directives` array. */
export function loadDirective<T>(index: number): T {
ngDevMode && assertNotNull(directives, 'Directives array should be defined if reading a dir.');
ngDevMode && assertDefined(directives, 'Directives array should be defined if reading a dir.');
ngDevMode && assertDataInRange(index, directives !);
return directives ![index];
}
@ -2453,7 +2462,7 @@ export function assertPreviousIsParent() {
}
function assertHasParent() {
assertNotNull(getParentLNode(previousOrParentNode), 'previousOrParentNode should have a parent');
assertDefined(getParentLNode(previousOrParentNode), 'previousOrParentNode should have a parent');
}
function assertDataInRange(index: number, arr?: any[]) {
@ -2484,9 +2493,9 @@ export function assertReservedSlotInitialized(slotOffset: number, numSlots: numb
}
export function _getComponentHostLElementNode<T>(component: T): LElementNode {
ngDevMode && assertNotNull(component, 'expecting component got null');
ngDevMode && assertDefined(component, 'expecting component got null');
const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
ngDevMode && assertNotNull(component, 'object is not a component');
ngDevMode && assertDefined(component, 'object is not a component');
return lElementNode;
}

View File

@ -420,10 +420,10 @@ export interface RootContext {
clean: Promise<null>;
/**
* RootComponent - The component which was instantiated by the call to
* RootComponents - The components that were instantiated by the call to
* {@link renderComponent}.
*/
component: {};
components: {}[];
}
/**

View File

@ -0,0 +1,73 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injector} from '../di/injector';
import {StaticProvider} from '../di/provider';
import {createInjector} from '../di/r3_injector';
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
import {InternalNgModuleRef, NgModuleFactory as viewEngine_NgModuleFactory, NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
import {Type} from '../type';
import {stringify} from '../util';
import {assertDefined} from './assert';
import {ComponentFactoryResolver} from './component_ref';
export interface NgModuleType { ngModuleDef: NgModuleDef; }
export interface NgModuleDef { bootstrap: Type<any>[]; }
export const COMPONENT_FACTORY_RESOLVER: StaticProvider = {
provide: viewEngine_ComponentFactoryResolver,
useFactory: () => new ComponentFactoryResolver(),
deps: [],
};
export class NgModuleRef<T> extends viewEngine_NgModuleRef<T> implements InternalNgModuleRef<T> {
// tslint:disable-next-line:require-internal-with-underscore
_bootstrapComponents: Type<any>[] = [];
injector: Injector;
componentFactoryResolver: viewEngine_ComponentFactoryResolver;
instance: T;
destroyCbs: (() => void)[]|null = [];
constructor(ngModuleType: Type<T>, parentInjector: Injector|null) {
super();
const ngModuleDef = (ngModuleType as any as NgModuleType).ngModuleDef;
ngDevMode && assertDefined(
ngModuleDef,
`NgModule '${stringify(ngModuleType)}' is not a subtype of 'NgModuleType'.`);
this._bootstrapComponents = ngModuleDef.bootstrap;
const additionalProviders: StaticProvider[] = [
COMPONENT_FACTORY_RESOLVER, {
provide: viewEngine_NgModuleRef,
useValue: this,
}
];
this.injector = createInjector(ngModuleType, parentInjector, additionalProviders);
this.instance = this.injector.get(ngModuleType);
this.componentFactoryResolver = new ComponentFactoryResolver();
}
destroy(): void {
ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed');
this.destroyCbs !.forEach(fn => fn());
this.destroyCbs = null;
}
onDestroy(callback: () => void): void {
ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed');
this.destroyCbs !.push(callback);
}
}
export class NgModuleFactory<T> extends viewEngine_NgModuleFactory<T> {
constructor(public moduleType: Type<T>) { super(); }
create(parentInjector: Injector|null): viewEngine_NgModuleRef<T> {
return new NgModuleRef(this.moduleType, parentInjector);
}
}

View File

@ -6,16 +6,16 @@
* found in the LICENSE file at https://angular.io/license
*/
import {assertEqual, assertNotNull} from './assert';
import {assertDefined, assertEqual} from './assert';
import {LNode, TNodeType} from './interfaces/node';
export function assertNodeType(node: LNode, type: TNodeType) {
assertNotNull(node, 'should be called with a node');
assertDefined(node, 'should be called with a node');
assertEqual(node.tNode.type, type, `should be a ${typeName(type)}`);
}
export function assertNodeOfPossibleTypes(node: LNode, ...types: TNodeType[]) {
assertNotNull(node, 'should be called with a node');
assertDefined(node, 'should be called with a node');
const found = types.some(type => node.tNode.type === type);
assertEqual(found, true, `Should be one of ${types.map(typeName).join(', ')}`);
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {assertNotNull} from './assert';
import {assertDefined} from './assert';
import {callHooks} from './hooks';
import {LContainer, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';

View File

@ -8,7 +8,7 @@
import './ng_dev_mode';
import {assertNotNull} from './assert';
import {assertDefined} from './assert';
import {AttributeMarker, TAttributes, TNode, unusedValueExportToPlacateAjd as unused1} from './interfaces/node';
import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection';
@ -36,7 +36,7 @@ function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string):
* @returns true if node matches the selector.
*/
export function isNodeMatchingSelector(tNode: TNode, selector: CssSelector): boolean {
ngDevMode && assertNotNull(selector[0], 'Selector should have a tag name');
ngDevMode && assertDefined(selector[0], 'Selector should have a tag name');
let mode: SelectorFlags = SelectorFlags.ELEMENT;
const nodeAttrs = tNode.attrs !;

View File

@ -15,7 +15,7 @@ import {QueryList as viewEngine_QueryList} from '../linker/query_list';
import {Type} from '../type';
import {getSymbolIterator} from '../util';
import {assertEqual, assertNotNull} from './assert';
import {assertDefined, assertEqual} from './assert';
import {ReadFromInjectorFn, getOrCreateNodeInjectorForNode} from './di';
import {assertPreviousIsParent, getCurrentQueries, store, storeCleanupWithContext} from './instructions';
import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
@ -163,7 +163,7 @@ export class LQueries_ implements LQueries {
let query = this.deep;
while (query) {
ngDevMode &&
assertNotNull(
assertDefined(
query.containerValues, 'View queries need to have a pointer to container values.');
query.containerValues !.splice(index, 0, query.values);
query = query.next;
@ -179,7 +179,7 @@ export class LQueries_ implements LQueries {
let query = this.deep;
while (query) {
ngDevMode &&
assertNotNull(
assertDefined(
query.containerValues, 'View queries need to have a pointer to container values.');
const removed = query.containerValues !.splice(index, 1);
@ -273,7 +273,7 @@ function add(query: LQuery<any>| null, node: LNode) {
if (directiveIdx !== null) {
// a node is matching a predicate - determine what to read
// note that queries using name selector must specify read strategy
ngDevMode && assertNotNull(predicate.read, 'the node should have a predicate');
ngDevMode && assertDefined(predicate.read, 'the node should have a predicate');
const result = readFromNodeInjector(nodeInjector, node, predicate.read !, directiveIdx);
if (result !== null) {
addMatch(query, result);

View File

@ -6,8 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref';
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref';
import {checkNoChanges, detectChanges, markViewDirty, storeCleanupFn} from './instructions';
import {ComponentTemplate} from './interfaces/definition';
@ -15,7 +17,15 @@ import {LViewNode} from './interfaces/node';
import {LView, LViewFlags} from './interfaces/view';
import {destroyLView} from './node_manipulation';
export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
// Needed due to tsickle downleveling where multiple `implements` with classes creates
// multiple @extends in Closure annotations, which is illegal. This workaround fixes
// the multiple @extends by making the annotation @implements instead
export interface viewEngine_ChangeDetectorRef_interface extends viewEngine_ChangeDetectorRef {}
export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_InternalViewRef,
viewEngine_ChangeDetectorRef_interface {
private _appRef: ApplicationRef|null;
context: T;
rootNodes: any[];
@ -210,6 +220,10 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
* introduce other changes.
*/
checkNoChanges(): void { checkNoChanges(this.context); }
detachFromAppRef() { this._appRef = null; }
attachToAppRef(appRef: ApplicationRef) { this._appRef = appRef; }
}

View File

@ -0,0 +1,95 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ApplicationModule, ApplicationRef, DoCheck, InjectFlags, InjectorType, Input, OnInit, PlatformRef, TestabilityRegistry, Type, defineInjector, inject, ɵE as elementStart, ɵNgModuleDef as NgModuleDef, ɵRenderFlags as RenderFlags, ɵT as text, ɵdefineComponent as defineComponent, ɵe as elementEnd, ɵi1 as interpolation1, ɵt as textBinding} from '@angular/core';
import {getTestBed, withBody} from '@angular/core/testing';
import {BrowserModule, EVENT_MANAGER_PLUGINS, platformBrowser} from '@angular/platform-browser';
import {BROWSER_MODULE_PROVIDERS} from '../../platform-browser/src/browser';
import {APPLICATION_MODULE_PROVIDERS} from '../src/application_module';
import {NgModuleFactory} from '../src/render3/ng_module_ref';
describe('ApplicationRef bootstrap', () => {
class HelloWorldComponent implements OnInit, DoCheck {
log: string[] = [];
name = 'World';
static ngComponentDef = defineComponent({
type: HelloWorldComponent,
selectors: [['hello-world']],
factory: () => new HelloWorldComponent(),
template: function(rf: RenderFlags, ctx: HelloWorldComponent): void {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
text(1);
elementEnd();
}
if (rf & RenderFlags.Update) {
textBinding(1, interpolation1('Hello ', ctx.name, ''));
}
}
});
ngOnInit(): void { this.log.push('OnInit'); }
ngDoCheck(): void { this.log.push('DoCheck'); }
}
class MyAppModule {
static ngInjectorDef =
defineInjector({factory: () => new MyAppModule(), imports: [BrowserModule]});
static ngModuleDef = defineNgModule({bootstrap: [HelloWorldComponent]});
}
it('should bootstrap hello world', withBody('<hello-world></hello-world>', async() => {
const MyAppModuleFactory = new NgModuleFactory(MyAppModule);
const moduleRef =
await getTestBed().platform.bootstrapModuleFactory(MyAppModuleFactory, {ngZone: 'noop'});
const appRef = moduleRef.injector.get(ApplicationRef);
const helloWorldComponent = appRef.components[0].instance as HelloWorldComponent;
expect(document.body.innerHTML).toEqual('<hello-world><div>Hello World</div></hello-world>');
// TODO(jasonaden): Get with Kara on lifecycle hooks
// expect(helloWorldComponent.log).toEqual(['OnInit', 'DoCheck']);
helloWorldComponent.name = 'Mundo';
appRef.tick();
expect(document.body.innerHTML).toEqual('<hello-world><div>Hello Mundo</div></hello-world>');
// TODO(jasonaden): Get with Kara on lifecycle hooks
// expect(helloWorldComponent.log).toEqual(['OnInit', 'DoCheck', 'DoCheck']);
// Cleanup TestabilityRegistry
const registry: TestabilityRegistry = getTestBed().get(TestabilityRegistry);
registry.unregisterAllApplications();
}));
});
/////////////////////////////////////////////////////////
// These go away when Compiler is ready
(BrowserModule as any as InjectorType<BrowserModule>).ngInjectorDef = defineInjector({
factory: function BrowserModule_Factory() {
return new BrowserModule(inject(BrowserModule, InjectFlags.Optional | InjectFlags.SkipSelf));
},
imports: [ApplicationModule],
providers: BROWSER_MODULE_PROVIDERS
});
(ApplicationModule as any as InjectorType<ApplicationModule>).ngInjectorDef = defineInjector({
factory: function ApplicationModule_Factory() {
return new ApplicationModule(inject(ApplicationRef));
},
providers: APPLICATION_MODULE_PROVIDERS
});
export function defineNgModule({bootstrap}: {bootstrap?: Type<any>[]}): NgModuleDef {
return {
bootstrap: bootstrap || [],
};
}
/////////////////////////////////////////////////////////

View File

@ -56,6 +56,9 @@
{
"name": "createLView"
},
{
"name": "createRootContext"
},
{
"name": "createTNode"
},

View File

@ -2418,7 +2418,7 @@
"name": "assertInterpolationSymbols"
},
{
"name": "assertNotNull$1"
"name": "assertNotNull"
},
{
"name": "assertPlatform"

View File

@ -266,6 +266,9 @@
{
"name": "createOutput"
},
{
"name": "createRootContext"
},
{
"name": "createTNode"
},
@ -657,7 +660,7 @@
"name": "throwMultipleComponentError"
},
{
"name": "tick"
"name": "tickRootContext"
},
{
"name": "trackByIdentity"

View File

@ -41,6 +41,8 @@ describe('InjectorDef-based createInjector()', () => {
const STATIC_TOKEN = new InjectionToken<StaticService>('STATIC_TOKEN');
const LOCALE = new InjectionToken<string[]>('LOCALE');
class ServiceWithDep {
constructor(readonly service: Service) {}
@ -50,6 +52,15 @@ describe('InjectorDef-based createInjector()', () => {
});
}
class ServiceWithMultiDep {
constructor(readonly locale: string[]) {}
static ngInjectableDef = defineInjectable({
providedIn: null,
factory: () => new ServiceWithMultiDep(inject(LOCALE)),
});
}
class ServiceTwo {
static ngInjectableDef = defineInjectable({
providedIn: null,
@ -112,6 +123,9 @@ describe('InjectorDef-based createInjector()', () => {
imports: [IntermediateModule],
providers: [
ServiceWithDep,
ServiceWithMultiDep,
{provide: LOCALE, multi: true, useValue: 'en'},
{provide: LOCALE, multi: true, useValue: 'es'},
Service,
{provide: SERVICE_TOKEN, useExisting: Service},
CircularA,
@ -168,6 +182,12 @@ describe('InjectorDef-based createInjector()', () => {
expect(instance.service).toBe(injector.get(Service));
});
it('injects a service with dependencies on multi-providers', () => {
const instance = injector.get(ServiceWithMultiDep);
expect(instance instanceof ServiceWithMultiDep);
expect(instance.locale).toEqual(['en', 'es']);
});
it('injects a token with useExisting', () => {
const instance = injector.get(SERVICE_TOKEN);
expect(instance).toBe(injector.get(Service));

View File

@ -7,9 +7,9 @@
*/
import {ComponentFactory, DoCheck, ViewEncapsulation, createInjector, defineInjectable, defineInjector} from '../../src/core';
import {DoCheck, ViewEncapsulation, createInjector, defineInjectable, defineInjector} from '../../src/core';
import {getRenderedText} from '../../src/render3/component';
import {LifecycleHooksFeature, defineComponent, directiveInject, markDirty} from '../../src/render3/index';
import {ComponentFactory, LifecycleHooksFeature, defineComponent, directiveInject, markDirty} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding, tick} from '../../src/render3/instructions';
import {ComponentDef, DirectiveDef, RenderFlags} from '../../src/render3/interfaces/definition';
import {createRendererType2} from '../../src/view/index';
@ -364,4 +364,27 @@ describe('recursive components', () => {
tick(comp);
expect(events).toEqual(['check6', 'check2', 'check0', 'check1', 'check5', 'check3', 'check4']);
});
it('should map inputs minified & unminified names', async() => {
class TestInputsComponent {
minifiedName: string;
static ngComponentDef = defineComponent({
type: TestInputsComponent,
selectors: [['test-inputs']],
inputs: {minifiedName: 'unminifiedName'},
factory: () => new TestInputsComponent(),
template: function(rf: RenderFlags, ctx: TestInputsComponent): void {
// Template not needed for this test
}
});
}
const testInputsComponentFactory = new ComponentFactory(TestInputsComponent.ngComponentDef);
expect([
{propName: 'minifiedName', templateName: 'unminifiedName'}
]).toEqual(testInputsComponentFactory.inputs);
});
});

View File

@ -135,7 +135,7 @@ export class ComponentFixture<T> extends BaseFixture {
// Fixtures above are preferred way of testing Components and Templates
///////////////////////////////////////////////////////////////////////////////////
export const document = ((global || window) as any).document;
export const document = ((typeof global == 'object' && global || window) as any).document;
export let containerEl: HTMLElement = null !;
let host: LElementNode|null;
const isRenderer2 =

View File

@ -7,7 +7,7 @@
*/
import {CommonModule, PlatformLocation, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common';
import {APP_ID, ApplicationModule, ErrorHandler, ModuleWithProviders, NgModule, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, RootRenderer, Sanitizer, SkipSelf, StaticProvider, Testability, createPlatformFactory, platformCore, ɵAPP_ROOT as APP_ROOT} from '@angular/core';
import {APP_ID, ApplicationModule, ClassProvider, ConstructorSansProvider, ErrorHandler, ExistingProvider, FactoryProvider, Inject, InjectionToken, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, RootRenderer, Sanitizer, SkipSelf, StaticProvider, Testability, TypeProvider, ValueProvider, createPlatformFactory, platformCore, ɵAPP_ROOT as APP_ROOT} from '@angular/core';
import {BrowserDomAdapter} from './browser/browser_adapter';
import {BrowserPlatformLocation} from './browser/location/browser_platform_location';
@ -20,7 +20,7 @@ import {getDOM} from './dom/dom_adapter';
import {DomRendererFactory2} from './dom/dom_renderer';
import {DOCUMENT} from './dom/dom_tokens';
import {DomEventsPlugin} from './dom/events/dom_events';
import {EVENT_MANAGER_PLUGINS, EventManager} from './dom/events/event_manager';
import {EVENT_MANAGER_PLUGINS, EventManager, EventManagerPlugin} from './dom/events/event_manager';
import {HAMMER_GESTURE_CONFIG, HammerGestureConfig, HammerGesturesPlugin} from './dom/events/hammer_gestures';
import {KeyEventsPlugin} from './dom/events/key_events';
import {DomSharedStylesHost, SharedStylesHost} from './dom/shared_styles_host';
@ -60,34 +60,47 @@ export function _document(): any {
return document;
}
export const BROWSER_MODULE_PROVIDERS: StaticProvider[] = [
BROWSER_SANITIZATION_PROVIDERS,
{provide: APP_ROOT, useValue: true},
{provide: ErrorHandler, useFactory: errorHandler, deps: []},
{
provide: EVENT_MANAGER_PLUGINS,
useClass: DomEventsPlugin,
multi: true,
deps: [DOCUMENT, NgZone, PLATFORM_ID]
},
{provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true, deps: [DOCUMENT]},
{
provide: EVENT_MANAGER_PLUGINS,
useClass: HammerGesturesPlugin,
multi: true,
deps: [DOCUMENT, HAMMER_GESTURE_CONFIG]
},
{provide: HAMMER_GESTURE_CONFIG, useClass: HammerGestureConfig, deps: []},
{
provide: DomRendererFactory2,
useClass: DomRendererFactory2,
deps: [EventManager, DomSharedStylesHost]
},
{provide: RendererFactory2, useExisting: DomRendererFactory2},
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
{provide: DomSharedStylesHost, useClass: DomSharedStylesHost, deps: [DOCUMENT]},
{provide: Testability, useClass: Testability, deps: [NgZone]},
{provide: EventManager, useClass: EventManager, deps: [EVENT_MANAGER_PLUGINS, NgZone]},
ELEMENT_PROBE_PROVIDERS,
{provide: Meta, useClass: Meta, deps: [DOCUMENT]},
{provide: Title, useClass: Title, deps: [DOCUMENT]},
];
/**
* The ng module for the browser.
*
*
*/
@NgModule({
providers: [
BROWSER_SANITIZATION_PROVIDERS,
{provide: APP_ROOT, useValue: true},
{provide: ErrorHandler, useFactory: errorHandler, deps: []},
{provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
{provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},
{provide: EVENT_MANAGER_PLUGINS, useClass: HammerGesturesPlugin, multi: true},
{provide: HAMMER_GESTURE_CONFIG, useClass: HammerGestureConfig},
DomRendererFactory2,
{provide: RendererFactory2, useExisting: DomRendererFactory2},
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
DomSharedStylesHost,
Testability,
EventManager,
ELEMENT_PROBE_PROVIDERS,
Meta,
Title,
],
exports: [CommonModule, ApplicationModule]
})
@NgModule({providers: BROWSER_MODULE_PROVIDERS, exports: [CommonModule, ApplicationModule]})
export class BrowserModule {
constructor(@Optional() @SkipSelf() parentModule: BrowserModule) {
constructor(@Optional() @SkipSelf() @Inject(BrowserModule) parentModule: BrowserModule|null) {
if (parentModule) {
throw new Error(
`BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead.`);

View File

@ -143,6 +143,11 @@ export declare abstract class ComponentRef<C> {
abstract onDestroy(callback: Function): void;
}
/** @experimental */
export interface ConstructorSansProvider {
deps?: any[];
}
export declare const ContentChild: ContentChildDecorator;
export interface ContentChildDecorator {
@ -168,7 +173,7 @@ export interface ContentChildrenDecorator {
}
/** @experimental */
export declare function createInjector(defType: any, parent?: Injector | null): Injector;
export declare function createInjector(defType: any, parent?: Injector | null, additionalProviders?: StaticProvider[] | null): Injector;
/** @experimental */
export declare function createPlatform(injector: Injector): PlatformRef;
@ -609,7 +614,7 @@ export interface Predicate<T> {
(value: T): boolean;
}
export declare type Provider = TypeProvider | ValueProvider | ClassProvider | ExistingProvider | FactoryProvider | any[];
export declare type Provider = TypeProvider | ValueProvider | ClassProvider | ConstructorProvider | ExistingProvider | FactoryProvider | any[];
export declare abstract class Query {
}

View File

@ -1,5 +1,5 @@
export declare class BrowserModule {
constructor(parentModule: BrowserModule);
constructor(parentModule: BrowserModule | null);
/** @experimental */ static withServerTransition(params: {
appId: string;
}): ModuleWithProviders;