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.
|
2017-12-20 10:47:22 -08:00
|
|
|
import {Injector} from '../di/injector';
|
|
|
|
import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
2017-12-14 15:03:46 -08:00
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
import {assertNotNull} from './assert';
|
2018-03-16 20:31:24 -07:00
|
|
|
import {queueInitHooks, queueLifecycleHooks} from './hooks';
|
|
|
|
import {CLEAN_PROMISE, _getComponentHostLElementNode, baseDirectiveCreate, createLView, createTView, enterView, getRootView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderComponentOrTemplate} from './instructions';
|
2018-01-22 15:27:21 -08:00
|
|
|
import {ComponentDef, ComponentType} from './interfaces/definition';
|
2018-03-16 18:50:48 -07:00
|
|
|
import {LElementNode} from './interfaces/node';
|
2018-02-26 16:58:15 -08:00
|
|
|
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
2018-03-08 16:55:47 -08:00
|
|
|
import {LView, LViewFlags, RootContext} from './interfaces/view';
|
2018-02-26 16:58:15 -08:00
|
|
|
import {stringify} from './util';
|
|
|
|
import {createViewRef} from './view_ref';
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-09 16:43:12 -08:00
|
|
|
|
2018-03-16 20:31:24 -07: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;
|
|
|
|
|
|
|
|
/**
|
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-03-06 11:58:08 -08:00
|
|
|
hostFeatures?: (<T>(component: T, componentDef: ComponentDef<T>) => void)[];
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2017-12-14 18:05:41 -08:00
|
|
|
* Bootstraps a component, then creates and returns a `ComponentRef` for that component.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
|
|
|
* @param componentType Component to bootstrap
|
|
|
|
* @param options Optional parameters which control bootstrapping
|
|
|
|
*/
|
|
|
|
export function createComponentRef<T>(
|
2017-12-20 10:47:22 -08:00
|
|
|
componentType: ComponentType<T>, opts: CreateComponentOptions): viewEngine_ComponentRef<T> {
|
2017-12-01 14:23:03 -08:00
|
|
|
const component = renderComponent(componentType, opts);
|
2018-03-08 16:55:47 -08:00
|
|
|
const hostView = _getComponentHostLElementNode(component).data as LView;
|
|
|
|
const hostViewRef = createViewRef(hostView, component);
|
2017-12-01 14:23:03 -08:00
|
|
|
return {
|
|
|
|
location: {nativeElement: getHostElement(component)},
|
|
|
|
injector: opts.injector || NULL_INJECTOR,
|
|
|
|
instance: component,
|
2018-03-08 16:55:47 -08:00
|
|
|
hostView: hostViewRef,
|
|
|
|
changeDetectorRef: hostViewRef,
|
2017-12-01 14:23:03 -08:00
|
|
|
componentType: componentType,
|
2017-12-14 18:05:41 -08:00
|
|
|
// TODO: implement destroy and onDestroy
|
|
|
|
destroy: () => {},
|
|
|
|
onDestroy: (cb: Function) => {}
|
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) => {
|
2017-12-01 14:23:03 -08:00
|
|
|
throw new Error('NullInjector: Not found: ' + stringify(token));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
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>(
|
2017-12-14 18:05:41 -08:00
|
|
|
componentType: ComponentType<T>, opts: CreateComponentOptions = {}): T {
|
2017-12-11 16:30:46 +01:00
|
|
|
const rendererFactory = opts.rendererFactory || domRendererFactory3;
|
2018-01-22 15:27:21 -08:00
|
|
|
const componentDef = componentType.ngComponentDef as ComponentDef<T>;
|
2018-01-09 16:43:12 -08:00
|
|
|
if (componentDef.type != componentType) componentDef.type = componentType;
|
2017-12-01 14:23:03 -08:00
|
|
|
let component: T;
|
2017-12-11 16:30:46 +01:00
|
|
|
const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag);
|
2018-02-03 20:34:30 -08:00
|
|
|
const rootContext: RootContext = {
|
|
|
|
// Incomplete initialization due to circular reference.
|
|
|
|
component: null !,
|
|
|
|
scheduler: opts.scheduler || requestAnimationFrame,
|
|
|
|
clean: CLEAN_PROMISE,
|
|
|
|
};
|
2018-03-16 18:50:48 -07:00
|
|
|
const rootView = createLView(
|
|
|
|
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView(), null,
|
|
|
|
rootContext, componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
|
|
|
|
|
|
|
|
const oldView = enterView(rootView, null !);
|
|
|
|
|
|
|
|
let elementNode: LElementNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
try {
|
2017-12-11 14:08:52 -08:00
|
|
|
// Create element node at index 0 in data array
|
2018-03-16 18:50:48 -07:00
|
|
|
elementNode = hostElement(hostNode, componentDef);
|
2017-12-11 14:08:52 -08:00
|
|
|
// Create directive instance with n() and store at index 1 in data array (el is 0)
|
2018-03-18 13:18:54 -07:00
|
|
|
component = rootContext.component =
|
|
|
|
baseDirectiveCreate(1, componentDef.factory(), componentDef) as T;
|
2018-03-06 10:13:49 -08:00
|
|
|
initChangeDetectorIfExisting(elementNode.nodeInjector, component);
|
2017-12-01 14:23:03 -08:00
|
|
|
} finally {
|
2018-03-06 10:13:49 -08:00
|
|
|
// We must not use leaveView here because it will set creationMode to false too early,
|
|
|
|
// causing init-only hooks not to run. The detectChanges call below will execute
|
|
|
|
// leaveView at the appropriate time in the lifecycle.
|
|
|
|
enterView(oldView, null);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-03-06 11:58:08 -08:00
|
|
|
opts.hostFeatures && opts.hostFeatures.forEach((feature) => feature(component, componentDef));
|
2018-03-16 18:50:48 -07:00
|
|
|
renderComponentOrTemplate(elementNode, rootView, component);
|
2017-12-01 14:23:03 -08:00
|
|
|
return component;
|
|
|
|
}
|
|
|
|
|
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-03-06 11:58:08 -08:00
|
|
|
export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void {
|
2018-03-06 10:13:49 -08:00
|
|
|
const elementNode = _getComponentHostLElementNode(component);
|
2018-03-16 20:31:24 -07:00
|
|
|
|
|
|
|
// Root component is always created at dir index 1, after host element at 0
|
|
|
|
queueInitHooks(1, def.onInit, def.doCheck, elementNode.view.tView);
|
2018-03-06 10:13:49 -08:00
|
|
|
queueLifecycleHooks(elementNode.flags, elementNode.view);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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-03-06 11:58:08 -08:00
|
|
|
const rootContext = getRootView(component).context as RootContext;
|
2018-02-03 20:34:30 -08:00
|
|
|
ngDevMode && assertNotNull(rootContext, 'rootContext');
|
|
|
|
return rootContext;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the host element of the component.
|
|
|
|
*
|
|
|
|
* Use this function to retrieve the host element of the component. The host
|
|
|
|
* element is the element which the component is associated with.
|
|
|
|
*
|
|
|
|
* @param component Component for which the host element should be retrieved.
|
|
|
|
*/
|
|
|
|
export function getHostElement<T>(component: T): HTMLElement {
|
|
|
|
return _getComponentHostLElementNode(component).native as any;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves the rendered text for a given component.
|
|
|
|
*
|
|
|
|
* This function retrieves the host element of a component and
|
|
|
|
* and then returns the `textContent` for that element. This implies
|
|
|
|
* that the text returned will include re-projected content of
|
|
|
|
* the component as well.
|
|
|
|
*
|
|
|
|
* @param component The component to return the content text for.
|
|
|
|
*/
|
|
|
|
export function getRenderedText(component: any): string {
|
|
|
|
const hostElement = getHostElement(component);
|
|
|
|
return hostElement.textContent || '';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
}
|