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';
|
|
|
|
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
|
2017-12-14 15:03:46 -08:00
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
import {assertNotNull} from './assert';
|
2018-01-08 21:57:50 -08:00
|
|
|
import {NG_HOST_SYMBOL, createError, createLView, directive, enterView, hostElement, leaveView, locateHostElement, renderComponentOrTemplate, directiveCreate} from './instructions';
|
2018-01-09 18:38:17 -08:00
|
|
|
import {ComponentDef, ComponentType} from './interfaces/definition';
|
|
|
|
import {LElementNode} from './interfaces/node';
|
|
|
|
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
2017-12-14 15:03:46 -08:00
|
|
|
import {notImplemented, stringify} from './util';
|
2017-12-01 14:23:03 -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;
|
|
|
|
|
|
|
|
/**
|
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.
|
|
|
|
*
|
|
|
|
* Example: PublicFeature is a function that makes the component public to the DI system.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
|
|
|
features?: (<T>(component: T, componentDef: ComponentDef<T>) => void)[];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
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);
|
2017-12-14 18:05:41 -08:00
|
|
|
const hostView = createViewRef(() => detectChanges(component), component);
|
2017-12-01 14:23:03 -08:00
|
|
|
return {
|
|
|
|
location: {nativeElement: getHostElement(component)},
|
|
|
|
injector: opts.injector || NULL_INJECTOR,
|
|
|
|
instance: component,
|
|
|
|
hostView: hostView,
|
|
|
|
changeDetectorRef: hostView,
|
|
|
|
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
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-12-14 18:05:41 -08:00
|
|
|
/**
|
|
|
|
* Creates an EmbeddedViewRef.
|
|
|
|
*
|
|
|
|
* @param detectChanges The detectChanges function for this view
|
|
|
|
* @param context The context for this view
|
|
|
|
* @returns The EmbeddedViewRef
|
|
|
|
*/
|
2017-12-01 14:23:03 -08:00
|
|
|
function createViewRef<T>(detectChanges: () => void, context: T): EmbeddedViewRef<T> {
|
2017-12-14 18:05:41 -08:00
|
|
|
return addDestroyable(new EmbeddedViewRef(detectChanges), context);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2017-12-20 10:47:22 -08:00
|
|
|
class EmbeddedViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
|
2017-12-14 18:05:41 -08:00
|
|
|
// TODO: rootNodes should be replaced when properly implemented
|
|
|
|
rootNodes = null !;
|
|
|
|
context: T;
|
|
|
|
destroyed: boolean;
|
|
|
|
|
|
|
|
constructor(public detectChanges: () => void) {}
|
|
|
|
|
|
|
|
// inherited from core/ChangeDetectorRef
|
|
|
|
markForCheck() {
|
|
|
|
if (ngDevMode) {
|
|
|
|
throw notImplemented();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
detach() {
|
|
|
|
if (ngDevMode) {
|
|
|
|
throw notImplemented();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
checkNoChanges() {
|
|
|
|
if (ngDevMode) {
|
|
|
|
throw notImplemented();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
reattach() {
|
|
|
|
if (ngDevMode) {
|
|
|
|
throw notImplemented();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
destroy(): void {}
|
|
|
|
|
|
|
|
onDestroy(cb: Function): void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Interface for destroy logic. Implemented by addDestroyable. */
|
2017-12-01 14:23:03 -08:00
|
|
|
interface DestroyRef<T> {
|
|
|
|
context: T;
|
2017-12-14 18:05:41 -08:00
|
|
|
/** Whether or not this object has been destroyed */
|
2017-12-01 14:23:03 -08:00
|
|
|
destroyed: boolean;
|
2017-12-14 18:05:41 -08:00
|
|
|
/** Destroy the instance and call all onDestroy callbacks. */
|
2017-12-01 14:23:03 -08:00
|
|
|
destroy(): void;
|
2017-12-14 18:05:41 -08:00
|
|
|
/** Register callbacks that should be called onDestroy */
|
2017-12-01 14:23:03 -08:00
|
|
|
onDestroy(cb: Function): void;
|
|
|
|
}
|
|
|
|
|
2017-12-14 18:05:41 -08:00
|
|
|
/**
|
|
|
|
* Decorates an object with destroy logic (implementing the DestroyRef interface)
|
|
|
|
* and returns the enhanced object.
|
|
|
|
*
|
|
|
|
* @param obj The object to decorate
|
|
|
|
* @returns The object with destroy logic
|
|
|
|
*/
|
2017-12-01 14:23:03 -08:00
|
|
|
function addDestroyable<T, C>(obj: any, context: C): T&DestroyRef<C> {
|
|
|
|
let destroyFn: Function[]|null = null;
|
|
|
|
obj.destroyed = false;
|
|
|
|
obj.destroy = function() {
|
|
|
|
destroyFn && destroyFn.forEach((fn) => fn());
|
|
|
|
this.destroyed = true;
|
|
|
|
};
|
|
|
|
obj.onDestroy = (fn: Function) => (destroyFn || (destroyFn = [])).push(fn);
|
2017-12-14 18:05:41 -08:00
|
|
|
obj.context = context;
|
2017-12-01 14:23:03 -08:00
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
*
|
|
|
|
* @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;
|
2017-12-01 14:23:03 -08:00
|
|
|
const componentDef = componentType.ngComponentDef;
|
|
|
|
let component: T;
|
2017-12-11 16:30:46 +01:00
|
|
|
const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag);
|
|
|
|
const oldView = enterView(
|
2018-01-08 20:17:13 -08:00
|
|
|
createLView(-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), []),
|
2017-12-11 16:30:46 +01:00
|
|
|
null !);
|
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
|
2017-12-11 16:30:46 +01:00
|
|
|
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-01-08 21:57:50 -08:00
|
|
|
component = directiveCreate(1, componentDef.n(), componentDef);
|
2017-12-01 14:23:03 -08:00
|
|
|
} finally {
|
|
|
|
leaveView(oldView);
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.features && opts.features.forEach((feature) => feature(component, componentDef));
|
|
|
|
detectChanges(component);
|
|
|
|
return component;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function detectChanges<T>(component: T) {
|
|
|
|
ngDevMode && assertNotNull(component, 'component');
|
2018-01-08 20:17:13 -08:00
|
|
|
const hostNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (ngDevMode && !hostNode) {
|
|
|
|
createError('Not a directive instance', component);
|
|
|
|
}
|
|
|
|
ngDevMode && assertNotNull(hostNode.data, 'hostNode.data');
|
2017-12-11 16:30:46 +01:00
|
|
|
renderComponentOrTemplate(hostNode, hostNode.view, component);
|
|
|
|
isDirty = false;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
let isDirty = false;
|
|
|
|
export function markDirty<T>(
|
|
|
|
component: T, scheduler: (fn: () => void) => void = requestAnimationFrame) {
|
|
|
|
ngDevMode && assertNotNull(component, 'component');
|
|
|
|
if (!isDirty) {
|
|
|
|
isDirty = true;
|
2017-12-14 18:05:41 -08:00
|
|
|
scheduler(() => detectChanges(component));
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getHostElement<T>(component: T): RElement {
|
2018-01-08 20:17:13 -08:00
|
|
|
return ((component as any)[NG_HOST_SYMBOL] as LElementNode).native;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|