2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2017-12-14 18:05:41 -08:00
|
|
|
// We are temporarily importing the existing viewEngine from core so we can be sure we are
|
|
|
|
// correctly implementing its interfaces for backwards compatibility.
|
2018-03-14 21:32:09 -07:00
|
|
|
import {Type} from '../core';
|
2017-12-20 10:47:22 -08:00
|
|
|
import {Injector} from '../di/injector';
|
2018-05-09 15:30:16 -07:00
|
|
|
import {Sanitizer} from '../sanitization/security';
|
2019-01-09 13:49:16 -08:00
|
|
|
import {assertDefined} from '../util/assert';
|
2017-12-14 15:03:46 -08:00
|
|
|
|
2019-01-09 13:49:16 -08:00
|
|
|
import {assertComponentType} from './assert';
|
2018-08-28 16:49:52 -07:00
|
|
|
import {getComponentDef} from './definition';
|
2018-10-18 09:23:18 +02:00
|
|
|
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
|
2018-11-07 13:03:11 -08:00
|
|
|
import {publishDefaultGlobalUtils} from './global_utils';
|
2019-01-16 09:35:35 -08:00
|
|
|
import {registerPostOrderHooks, registerPreOrderHooks} from './hooks';
|
2019-01-28 14:52:37 -08:00
|
|
|
import {addToViewTree, CLEAN_PROMISE, createLView, createNodeAtIndex, createTNode, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews,} from './instructions';
|
2018-11-27 12:05:26 -08:00
|
|
|
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
|
2018-11-28 15:54:38 -08:00
|
|
|
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
|
2018-10-08 16:04:46 -07:00
|
|
|
import {PlayerHandler} from './interfaces/player';
|
2018-10-26 12:27:40 +02:00
|
|
|
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
2019-01-30 23:42:26 +00:00
|
|
|
import {CONTEXT, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, T_HOST} from './interfaces/view';
|
2018-11-27 12:05:26 -08:00
|
|
|
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setCurrentDirectiveDef} from './state';
|
2019-01-12 00:59:48 -08:00
|
|
|
import {defaultScheduler, getRootView, readPatchedLView, renderStringify} from './util';
|
2018-08-28 16:49:52 -07:00
|
|
|
|
2018-01-09 16:43:12 -08:00
|
|
|
|
2018-11-15 08:43:56 -08:00
|
|
|
|
2017-12-14 18:05:41 -08:00
|
|
|
/** Options that control how the component should be bootstrapped. */
|
|
|
|
export interface CreateComponentOptions {
|
|
|
|
/** Which renderer factory to use. */
|
2017-12-01 14:23:03 -08:00
|
|
|
rendererFactory?: RendererFactory3;
|
|
|
|
|
2018-05-09 15:30:16 -07:00
|
|
|
/** A custom sanitizer instance */
|
|
|
|
sanitizer?: Sanitizer;
|
|
|
|
|
2018-08-28 16:49:52 -07:00
|
|
|
/** A custom animation player handler */
|
|
|
|
playerHandler?: PlayerHandler;
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
2017-12-14 18:05:41 -08:00
|
|
|
* Host element on which the component will be bootstrapped. If not specified,
|
2017-12-01 14:23:03 -08:00
|
|
|
* the component definition's `tag` is used to query the existing DOM for the
|
|
|
|
* element to bootstrap.
|
|
|
|
*/
|
|
|
|
host?: RElement|string;
|
|
|
|
|
2017-12-14 18:05:41 -08:00
|
|
|
/** Module injector for the component. If unspecified, the injector will be NULL_INJECTOR. */
|
2017-12-20 10:47:22 -08:00
|
|
|
injector?: Injector;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
/**
|
2017-12-14 18:05:41 -08:00
|
|
|
* List of features to be applied to the created component. Features are simply
|
|
|
|
* functions that decorate a component with a certain behavior.
|
|
|
|
*
|
2018-03-06 10:13:49 -08:00
|
|
|
* Typically, the features in this list are features that cannot be added to the
|
|
|
|
* other features list in the component definition because they rely on other factors.
|
|
|
|
*
|
2018-03-06 11:58:08 -08:00
|
|
|
* Example: `RootLifecycleHooks` is a function that adds lifecycle hook capabilities
|
2018-03-06 10:13:49 -08:00
|
|
|
* to root components in a tree-shakable way. It cannot be added to the component
|
|
|
|
* features list because there's no way of knowing when the component will be used as
|
|
|
|
* a root component.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-09-13 16:07:23 -07:00
|
|
|
hostFeatures?: HostFeature[];
|
2018-02-03 20:34:30 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A function which is used to schedule change detection work in the future.
|
|
|
|
*
|
|
|
|
* When marking components as dirty, it is necessary to schedule the work of
|
|
|
|
* change detection in the future. This is done to coalesce multiple
|
|
|
|
* {@link markDirty} calls into a single changed detection processing.
|
|
|
|
*
|
|
|
|
* The default value of the scheduler is the `requestAnimationFrame` function.
|
|
|
|
*
|
|
|
|
* It is also useful to override this function for testing purposes.
|
|
|
|
*/
|
|
|
|
scheduler?: (work: () => void) => void;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-09-13 16:07:23 -07:00
|
|
|
/** See CreateComponentOptions.hostFeatures */
|
2018-09-21 12:12:06 -07:00
|
|
|
type HostFeature = (<T>(component: T, componentDef: ComponentDef<T>) => void);
|
2018-09-13 16:07:23 -07:00
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
// TODO: A hack to not pull in the NullInjector from @angular/core.
|
2017-12-20 10:47:22 -08:00
|
|
|
export const NULL_INJECTOR: Injector = {
|
2017-12-14 18:05:41 -08:00
|
|
|
get: (token: any, notFoundValue?: any) => {
|
2019-01-12 00:59:48 -08:00
|
|
|
throw new Error('NullInjector: Not found: ' + renderStringify(token));
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-12-14 18:05:41 -08:00
|
|
|
* Bootstraps a Component into an existing host element and returns an instance
|
|
|
|
* of the component.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-02-03 20:34:30 -08:00
|
|
|
* Use this function to bootstrap a component into the DOM tree. Each invocation
|
|
|
|
* of this function will create a separate tree of components, injectors and
|
|
|
|
* change detection cycles and lifetimes. To dynamically insert a new component
|
|
|
|
* into an existing tree such that it shares the same injection, change detection
|
|
|
|
* and object lifetime, use {@link ViewContainer#createComponent}.
|
|
|
|
*
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param componentType Component to bootstrap
|
|
|
|
* @param options Optional parameters which control bootstrapping
|
|
|
|
*/
|
|
|
|
export function renderComponent<T>(
|
2018-03-14 21:32:09 -07:00
|
|
|
componentType: ComponentType<T>|
|
|
|
|
Type<T>/* Type as workaround for: Microsoft/TypeScript/issues/4881 */
|
|
|
|
,
|
|
|
|
opts: CreateComponentOptions = {}): T {
|
2018-10-23 15:33:01 -07:00
|
|
|
ngDevMode && publishDefaultGlobalUtils();
|
2018-03-14 21:32:09 -07:00
|
|
|
ngDevMode && assertComponentType(componentType);
|
2017-12-11 16:30:46 +01:00
|
|
|
const rendererFactory = opts.rendererFactory || domRendererFactory3;
|
2018-05-09 15:30:16 -07:00
|
|
|
const sanitizer = opts.sanitizer || null;
|
2018-08-29 16:34:44 -07:00
|
|
|
const componentDef = getComponentDef<T>(componentType) !;
|
2018-01-09 16:43:12 -08:00
|
|
|
if (componentDef.type != componentType) componentDef.type = componentType;
|
2018-08-03 12:30:40 -07:00
|
|
|
|
2018-03-29 16:41:45 -07:00
|
|
|
// The first index of the first selector is the tag name.
|
|
|
|
const componentTag = componentDef.selectors ![0] ![0] as string;
|
2018-10-12 15:02:54 -07:00
|
|
|
const hostRNode = locateHostElement(rendererFactory, opts.host || componentTag);
|
2018-09-13 16:07:23 -07:00
|
|
|
const rootFlags = componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
|
|
|
|
LViewFlags.CheckAlways | LViewFlags.IsRoot;
|
2018-10-26 12:27:40 +02:00
|
|
|
const rootContext = createRootContext(opts.scheduler, opts.playerHandler);
|
2018-05-09 16:49:39 -07:00
|
|
|
|
2018-10-12 15:02:54 -07:00
|
|
|
const renderer = rendererFactory.createRenderer(hostRNode, componentDef);
|
2018-11-21 21:14:06 -08:00
|
|
|
const rootView: LView = createLView(
|
2019-01-30 23:42:26 +00:00
|
|
|
null, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, null, null,
|
|
|
|
rendererFactory, renderer, undefined, opts.injector || null);
|
2018-03-16 18:50:48 -07:00
|
|
|
|
2018-09-05 16:15:37 -07:00
|
|
|
const oldView = enterView(rootView, null);
|
2018-08-03 12:30:40 -07:00
|
|
|
let component: T;
|
2017-12-01 14:23:03 -08:00
|
|
|
try {
|
2018-03-25 21:32:39 -07:00
|
|
|
if (rendererFactory.begin) rendererFactory.begin();
|
2018-11-21 21:14:06 -08:00
|
|
|
const componentView = createRootComponentView(
|
|
|
|
hostRNode, componentDef, rootView, rendererFactory, renderer, sanitizer);
|
2018-09-13 16:07:23 -07:00
|
|
|
component = createRootComponent(
|
2018-10-26 12:27:40 +02:00
|
|
|
componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
|
2018-03-25 21:32:39 -07:00
|
|
|
|
2019-01-28 14:52:37 -08:00
|
|
|
addToViewTree(rootView, HEADER_OFFSET, componentView);
|
|
|
|
|
2018-12-18 16:58:51 -08:00
|
|
|
refreshDescendantViews(rootView); // creation mode pass
|
|
|
|
rootView[FLAGS] &= ~LViewFlags.CreationMode;
|
|
|
|
refreshDescendantViews(rootView); // update mode pass
|
2017-12-01 14:23:03 -08:00
|
|
|
} finally {
|
2018-03-25 21:32:39 -07:00
|
|
|
leaveView(oldView);
|
|
|
|
if (rendererFactory.end) rendererFactory.end();
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return component;
|
|
|
|
}
|
|
|
|
|
2018-10-12 15:02:54 -07:00
|
|
|
/**
|
|
|
|
* Creates the root component view and the root component node.
|
|
|
|
*
|
|
|
|
* @param rNode Render host element.
|
|
|
|
* @param def ComponentDef
|
|
|
|
* @param rootView The parent view where the host node is stored
|
|
|
|
* @param renderer The current renderer
|
|
|
|
* @param sanitizer The sanitizer, if provided
|
|
|
|
*
|
|
|
|
* @returns Component view created
|
|
|
|
*/
|
|
|
|
export function createRootComponentView(
|
2018-11-21 21:14:06 -08:00
|
|
|
rNode: RElement | null, def: ComponentDef<any>, rootView: LView,
|
|
|
|
rendererFactory: RendererFactory3, renderer: Renderer3, sanitizer?: Sanitizer | null): LView {
|
2018-10-12 15:02:54 -07:00
|
|
|
resetComponentState();
|
|
|
|
const tView = rootView[TVIEW];
|
2019-01-30 23:42:26 +00:00
|
|
|
const tNode: TElementNode = createNodeAtIndex(0, TNodeType.Element, rNode, null, null);
|
2018-11-21 21:14:06 -08:00
|
|
|
const componentView = createLView(
|
2018-11-21 21:14:06 -08:00
|
|
|
rootView,
|
2018-10-12 15:02:54 -07:00
|
|
|
getOrCreateTView(
|
|
|
|
def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery),
|
2019-01-30 23:42:26 +00:00
|
|
|
null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[HEADER_OFFSET], tNode,
|
|
|
|
rendererFactory, renderer, sanitizer);
|
2018-10-12 15:02:54 -07:00
|
|
|
|
|
|
|
if (tView.firstTemplatePass) {
|
2018-10-18 09:23:18 +02:00
|
|
|
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), rootView, def.type);
|
|
|
|
tNode.flags = TNodeFlags.isComponent;
|
|
|
|
initNodeFlags(tNode, rootView.length, 1);
|
2018-10-30 22:10:23 -07:00
|
|
|
queueComponentIndexForCheck(tNode);
|
2018-10-12 15:02:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Store component view at node index, with node as the HOST
|
|
|
|
return rootView[HEADER_OFFSET] = componentView;
|
|
|
|
}
|
|
|
|
|
2018-09-13 16:07:23 -07:00
|
|
|
/**
|
|
|
|
* Creates a root component and sets it up with features and host bindings. Shared by
|
|
|
|
* renderComponent() and ViewContainerRef.createComponent().
|
|
|
|
*/
|
|
|
|
export function createRootComponent<T>(
|
2018-11-21 21:14:06 -08:00
|
|
|
componentView: LView, componentDef: ComponentDef<T>, rootView: LView, rootContext: RootContext,
|
|
|
|
hostFeatures: HostFeature[] | null): any {
|
2018-10-18 09:23:18 +02:00
|
|
|
const tView = rootView[TVIEW];
|
2018-10-08 16:04:46 -07:00
|
|
|
// Create directive instance with factory() and store at next index in viewData
|
2018-10-18 09:23:18 +02:00
|
|
|
const component = instantiateRootComponent(tView, rootView, componentDef);
|
2018-09-13 16:07:23 -07:00
|
|
|
|
|
|
|
rootContext.components.push(component);
|
2018-10-12 15:02:54 -07:00
|
|
|
componentView[CONTEXT] = component;
|
2018-09-13 16:07:23 -07:00
|
|
|
|
|
|
|
hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef));
|
2018-10-18 09:23:18 +02:00
|
|
|
|
2018-11-27 12:05:26 -08:00
|
|
|
if (tView.firstTemplatePass && componentDef.hostBindings) {
|
|
|
|
const rootTNode = getPreviousOrParentTNode();
|
|
|
|
setCurrentDirectiveDef(componentDef);
|
2018-12-15 15:57:57 -08:00
|
|
|
componentDef.hostBindings(RenderFlags.Create, component, rootTNode.index - HEADER_OFFSET);
|
2018-11-27 12:05:26 -08:00
|
|
|
setCurrentDirectiveDef(null);
|
|
|
|
}
|
|
|
|
|
2018-09-13 16:07:23 -07:00
|
|
|
return component;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-08-28 16:49:52 -07:00
|
|
|
export function createRootContext(
|
2018-10-26 12:27:40 +02:00
|
|
|
scheduler?: (workFn: () => void) => void, playerHandler?: PlayerHandler|null): RootContext {
|
2018-05-09 16:49:39 -07:00
|
|
|
return {
|
|
|
|
components: [],
|
2018-10-26 12:27:40 +02:00
|
|
|
scheduler: scheduler || defaultScheduler,
|
2018-05-09 16:49:39 -07:00
|
|
|
clean: CLEAN_PROMISE,
|
2018-08-28 16:49:52 -07:00
|
|
|
playerHandler: playerHandler || null,
|
|
|
|
flags: RootContextFlags.Empty
|
2018-05-09 16:49:39 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-02-03 20:34:30 -08:00
|
|
|
/**
|
2018-03-06 10:13:49 -08:00
|
|
|
* Used to enable lifecycle hooks on the root component.
|
|
|
|
*
|
|
|
|
* Include this feature when calling `renderComponent` if the root component
|
|
|
|
* you are rendering has lifecycle hooks defined. Otherwise, the hooks won't
|
|
|
|
* be called properly.
|
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
*
|
2018-03-06 11:58:08 -08:00
|
|
|
* ```
|
2018-03-06 10:13:49 -08:00
|
|
|
* renderComponent(AppComponent, {features: [RootLifecycleHooks]});
|
2018-03-06 11:58:08 -08:00
|
|
|
* ```
|
2018-03-06 10:13:49 -08:00
|
|
|
*/
|
2018-09-21 12:12:06 -07:00
|
|
|
export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void {
|
2018-11-21 21:14:06 -08:00
|
|
|
const rootTView = readPatchedLView(component) ![TVIEW];
|
2018-10-08 16:04:46 -07:00
|
|
|
const dirIndex = rootTView.data.length - 1;
|
2018-03-16 20:31:24 -07:00
|
|
|
|
2018-12-20 17:23:25 -08:00
|
|
|
registerPreOrderHooks(dirIndex, def, rootTView);
|
2018-11-28 15:54:38 -08:00
|
|
|
// TODO(misko): replace `as TNode` with createTNode call. (needs refactoring to lose dep on
|
|
|
|
// LNode).
|
2019-01-16 09:35:35 -08:00
|
|
|
registerPostOrderHooks(
|
|
|
|
rootTView, { directiveStart: dirIndex, directiveEnd: dirIndex + 1 } as TNode);
|
2018-03-06 10:13:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the root context for any component by walking the parent `LView` until
|
2018-02-03 20:34:30 -08:00
|
|
|
* reaching the root `LView`.
|
|
|
|
*
|
|
|
|
* @param component any component
|
|
|
|
*/
|
|
|
|
function getRootContext(component: any): RootContext {
|
2018-06-07 22:42:32 -07:00
|
|
|
const rootContext = getRootView(component)[CONTEXT] as RootContext;
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(rootContext, 'rootContext');
|
2018-02-03 20:34:30 -08:00
|
|
|
return rootContext;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wait on component until it is rendered.
|
|
|
|
*
|
|
|
|
* This function returns a `Promise` which is resolved when the component's
|
|
|
|
* change detection is executed. This is determined by finding the scheduler
|
|
|
|
* associated with the `component`'s render tree and waiting until the scheduler
|
|
|
|
* flushes. If nothing is scheduled, the function returns a resolved promise.
|
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
* ```
|
|
|
|
* await whenRendered(myComponent);
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* @param component Component to wait upon
|
|
|
|
* @returns Promise which resolves when the component is rendered.
|
|
|
|
*/
|
|
|
|
export function whenRendered(component: any): Promise<null> {
|
|
|
|
return getRootContext(component).clean;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|