/** * @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 {ComponentRef, EmbeddedViewRef, Injector} from '../core'; import {assertNotNull} from './assert'; import {NG_HOST_SYMBOL, createError, createViewState, directiveCreate, elementHost, enterView, leaveView} from './instructions'; import {LElement} from './interfaces'; import {ComponentDef, ComponentType} from './public_interfaces'; import {RElement, Renderer3, RendererFactory3} from './renderer'; import {stringify} from './util'; /** * Options which control how the component should be bootstrapped. */ export interface CreateComponentOptionArgs { /** * Which renderer to use. */ renderer?: Renderer3; rendererFactory?: RendererFactory3; /** * Which host element should the component be bootstrapped on. If not specified * the component definition's `tag` is used to query the existing DOM for the * element to bootstrap. */ host?: RElement|string; /** * Optional Injector which is the Module Injector for the component. */ injector?: Injector; /** * a set of features which should be applied to this component. */ features?: ((component: T, componentDef: ComponentDef) => void)[]; } /** * Bootstrap a Component into an existing host element and return `ComponentRef`. * * @param componentType Component to bootstrap * @param options Optional parameters which control bootstrapping */ export function createComponentRef( componentType: ComponentType, opts: CreateComponentOptionArgs): ComponentRef { const component = renderComponent(componentType, opts); const hostView = createViewRef(detectChanges.bind(component), component); return { location: {nativeElement: getHostElement(component)}, injector: opts.injector || NULL_INJECTOR, instance: component, hostView: hostView, changeDetectorRef: hostView, componentType: componentType, destroy: function() {}, onDestroy: function(cb: Function): void {} }; } function createViewRef(detectChanges: () => void, context: T): EmbeddedViewRef { return addDestroyable( { rootNodes: null !, // inherited from core/ChangeDetectorRef markForCheck: () => { if (ngDevMode) { implement(); } }, detach: () => { if (ngDevMode) { implement(); } }, detectChanges: detectChanges, checkNoChanges: () => { if (ngDevMode) { implement(); } }, reattach: () => { if (ngDevMode) { implement(); } }, }, context); } interface DestroyRef { context: T; destroyed: boolean; destroy(): void; onDestroy(cb: Function): void; } function implement() { throw new Error('NotImplemented'); } function addDestroyable(obj: any, context: C): T&DestroyRef { 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); return obj; } // TODO: A hack to not pull in the NullInjector from @angular/core. export const NULL_INJECTOR: Injector = { get: function(token: any, notFoundValue?: any) { throw new Error('NullInjector: Not found: ' + stringify(token)); } }; /** * Bootstrap a Component into an existing host element and return `NgComponent`. * * NgComponent is a light weight Custom Elements inspired API for bootstrapping and * interacting with bootstrapped component. * * @param componentType Component to bootstrap * @param options Optional parameters which control bootstrapping */ export function renderComponent( componentType: ComponentType, opts: CreateComponentOptionArgs = {}): T { const renderer = opts.renderer || document; const componentDef = componentType.ngComponentDef; let component: T; const oldView = enterView(createViewState(-1, renderer), null); try { elementHost(opts.host || componentDef.tag); component = directiveCreate(0, componentDef.n(), componentDef); } finally { leaveView(oldView); } opts.features && opts.features.forEach((feature) => feature(component, componentDef)); detectChanges(component); return component; } export function detectChanges(component: T) { ngDevMode && assertNotNull(component, 'component'); const hostNode = (component as any)[NG_HOST_SYMBOL] as LElement; if (ngDevMode && !hostNode) { createError('Not a directive instance', component); } ngDevMode && assertNotNull(hostNode.data, 'hostNode.data'); const oldView = enterView(hostNode.view !, hostNode); try { (component.constructor as ComponentType).ngComponentDef.r(0, 0); isDirty = false; } finally { leaveView(oldView); } } let isDirty = false; export function markDirty( component: T, scheduler: (fn: () => void) => void = requestAnimationFrame) { ngDevMode && assertNotNull(component, 'component'); if (!isDirty) { isDirty = true; scheduler(detectChanges.bind(null, component)); } } export function getHostElement(component: T): RElement { return ((component as any)[NG_HOST_SYMBOL] as LElement).native; }