feat(core): add renderer factory in render3 (#20855)

PR Close #20855
This commit is contained in:
Marc Laval
2017-12-11 16:30:46 +01:00
committed by Igor Minar
parent 147aec43bd
commit d1de587ce0
15 changed files with 342 additions and 76 deletions

View File

@ -9,10 +9,10 @@
import {ComponentRef, EmbeddedViewRef, Injector} from '../core';
import {assertNotNull} from './assert';
import {NG_HOST_SYMBOL, createError, createViewState, directive, elementHost, enterView, leaveView} from './instructions';
import {NG_HOST_SYMBOL, createError, createViewState, directive, enterView, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions';
import {LElement} from './l_node';
import {ComponentDef, ComponentType} from './public_interfaces';
import {RElement, Renderer3, RendererFactory3} from './renderer';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './renderer';
import {notImplemented, stringify} from './util';
@ -22,10 +22,8 @@ import {notImplemented, stringify} from './util';
*/
export interface CreateComponentOptionArgs {
/**
* Which renderer to use.
* Which renderer factory to use.
*/
renderer?: Renderer3;
rendererFactory?: RendererFactory3;
/**
@ -138,13 +136,16 @@ export const NULL_INJECTOR: Injector = {
*/
export function renderComponent<T>(
componentType: ComponentType<T>, opts: CreateComponentOptionArgs = {}): T {
const renderer = opts.renderer || document;
const rendererFactory = opts.rendererFactory || domRendererFactory3;
const componentDef = componentType.ngComponentDef;
let component: T;
const oldView = enterView(createViewState(-1, renderer, []), null);
const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag);
const oldView = enterView(
createViewState(-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), []),
null !);
try {
// Create element node at index 0 in data array
elementHost(opts.host || componentDef.tag, componentDef);
hostElement(hostNode, componentDef);
// Create directive instance with n() and store at index 1 in data array (el is 0)
component = directive(1, componentDef.n(), componentDef);
} finally {
@ -163,15 +164,8 @@ export function detectChanges<T>(component: T) {
createError('Not a directive instance', component);
}
ngDevMode && assertNotNull(hostNode.data, 'hostNode.data');
const oldView = enterView(hostNode.view !, hostNode);
try {
// Element was stored at 0 and directive was stored at 1 in renderComponent
// so to refresh the component, r() needs to be called with (1, 0)
(component.constructor as ComponentType<T>).ngComponentDef.r(1, 0);
isDirty = false;
} finally {
leaveView(oldView);
}
renderComponentOrTemplate(hostNode, hostNode.view, component);
isDirty = false;
}
let isDirty = false;

View File

@ -18,9 +18,9 @@ import {NgStaticData, LNodeStatic, LContainerStatic, InitialInputData, InitialIn
import {assertNodeType} from './node_assert';
import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation';
import {isNodeMatchingSelector} from './node_selector_matcher';
import {ComponentDef, ComponentTemplate, DirectiveDef} from './public_interfaces';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef} from './public_interfaces';
import {QueryList, QueryState_} from './query';
import {RComment, RElement, RText, Renderer3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3} from './renderer';
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3} from './renderer';
import {isDifferent, stringify} from './util';
export {queryRefresh} from './query';
@ -73,6 +73,7 @@ let nextNgElementId = 0;
* Renderer2.
*/
let renderer: Renderer3;
let rendererFactory: RendererFactory3;
/** Used to set the parent property when nodes are created. */
let previousOrParentNode: LNode;
@ -278,18 +279,44 @@ export function createLNode(
/**
*
* @param host Existing node to render into.
* @param renderer Renderer to use.
* @param template Template function with the instructions.
* @param context to pass into the template.
*/
export function renderTemplate<T>(host: LElement, template: ComponentTemplate<T>, context: T) {
export function renderTemplate<T>(
hostNode: RElement, template: ComponentTemplate<T>, context: T,
providedRendererFactory: RendererFactory3, host: LElement | null): LElement {
if (host == null) {
rendererFactory = providedRendererFactory;
host = createLNode(
null, LNodeFlags.Element, hostNode,
createViewState(-1, providedRendererFactory.createRenderer(null, null), []));
}
const hostView = host.data !;
ngDevMode && assertNotEqual(hostView, null, 'hostView');
hostView.ngStaticData = getTemplateStatic(template);
const oldView = enterView(hostView, host);
renderComponentOrTemplate(host, hostView, context, template);
return host;
}
export function renderComponentOrTemplate<T>(
node: LElement, viewState: ViewState, componentOrContext: T, template?: ComponentTemplate<T>) {
const oldView = enterView(viewState, node);
try {
template(context, creationMode);
if (rendererFactory.begin) {
rendererFactory.begin();
}
if (template) {
ngStaticData = template.ngStaticData || (template.ngStaticData = [] as never);
template(componentOrContext !, creationMode);
} else {
// Element was stored at 0 and directive was stored at 1 in renderComponent
// so to refresh the component, r() needs to be called with (1, 0)
(componentOrContext.constructor as ComponentType<T>).ngComponentDef.r(1, 0);
}
} finally {
if (rendererFactory.end) {
rendererFactory.end();
}
leaveView(oldView);
}
}
@ -406,7 +433,10 @@ export function elementStart(
let componentView: ViewState|null = null;
if (isHostElement) {
const ngStaticData = getTemplateStatic((nameOrComponentDef as ComponentDef<any>).template);
componentView = addToViewTree(createViewState(-1, renderer, ngStaticData));
componentView = addToViewTree(createViewState(
-1, rendererFactory.createRenderer(
native, (nameOrComponentDef as ComponentDef<any>).rendererType),
ngStaticData));
}
// Only component views should be added to the view tree directly. Embedded views are
@ -453,16 +483,19 @@ export function createError(text: string, token: any) {
/**
* Used for bootstrapping existing nodes into rendering pipeline.
* Locates the host native element, used for bootstrapping existing nodes into rendering pipeline.
*
* @param elementOrSelector Render element or CSS selector to locate the element.
*/
export function elementHost(elementOrSelector: RElement | string, def: ComponentDef<any>) {
export function locateHostElement(
factory: RendererFactory3, elementOrSelector: RElement | string): RElement|null {
ngDevMode && assertDataInRange(-1);
rendererFactory = factory;
const defaultRenderer = factory.createRenderer(null, null);
const rNode = typeof elementOrSelector === 'string' ?
((renderer as ProceduralRenderer3).selectRootElement ?
(renderer as ProceduralRenderer3).selectRootElement(elementOrSelector) :
(renderer as ObjectOrientedRenderer3).querySelector !(elementOrSelector)) :
((defaultRenderer as ProceduralRenderer3).selectRootElement ?
(defaultRenderer as ProceduralRenderer3).selectRootElement(elementOrSelector) :
(defaultRenderer as ObjectOrientedRenderer3).querySelector !(elementOrSelector)) :
elementOrSelector;
if (ngDevMode && !rNode) {
if (typeof elementOrSelector === 'string') {
@ -471,6 +504,15 @@ export function elementHost(elementOrSelector: RElement | string, def: Component
throw createError('Host node is required:', elementOrSelector);
}
}
return rNode;
}
/**
* Creates the host LNode..
*
* @param rNode Render host element.
*/
export function hostElement(rNode: RElement | null, def: ComponentDef<any>) {
createLNode(
0, LNodeFlags.Element, rNode, createViewState(-1, renderer, getTemplateStatic(def.template)));
}

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '../core';
import {RendererType2, Type} from '../core';
import {resolveRendererType2} from '../view/util';
import {componentRefresh, diPublic} from './instructions';
@ -108,6 +108,13 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
* NOTE: only used with component directives.
*/
template: ComponentTemplate<T>;
/**
* Renderer type data of the component.
*
* NOTE: only used with component directives.
*/
rendererType: RendererType2|null;
}
export interface DirectiveDefArgs<T> {
@ -125,6 +132,7 @@ export interface ComponentDefArgs<T> extends DirectiveDefArgs<T> {
template: ComponentTemplate<T>;
refresh?: (this: ComponentDef<T>, directiveIndex: number, elementIndex: number) => void;
features?: ComponentDefFeature[];
rendererType?: RendererType2;
}
export type DirectiveDefFeature = <T>(directiveDef: DirectiveDef<T>) => void;
@ -156,6 +164,7 @@ export function defineComponent<T>(componentDefinition: ComponentDefArgs<T>): Co
inputs: invertObject(componentDefinition.inputs),
outputs: invertObject(componentDefinition.outputs),
methods: invertObject(componentDefinition.methods),
rendererType: resolveRendererType2(componentDefinition.rendererType) || null,
};
const feature = componentDefinition.features;
feature && feature.forEach((fn) => fn(def));

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import {DirectiveDef} from '@angular/core/src/render3/public_interfaces';
import {Observable} from 'rxjs/Observable';
import * as viewEngine from '../core';
@ -15,6 +14,7 @@ import {assertNotNull} from './assert';
import {injectElementRefForNode} from './di';
import {QueryState} from './interfaces';
import {LContainer, LElement, LNode, LNodeFlags, LView} from './l_node';
import {DirectiveDef} from './public_interfaces';

View File

@ -15,7 +15,7 @@
* it will be easy to implement such API.
*/
import {RendererStyleFlags2} from '../core';
import {RendererStyleFlags2, RendererType2, ViewEncapsulation} from '../core';
import {ComponentDef} from './public_interfaces';
// TODO: cleanup once the code is merged in angular/angular
@ -68,11 +68,16 @@ export interface ProceduralRenderer3 {
}
export interface RendererFactory3 {
createRenderer(hostElement: RElement, componentDef: ComponentDef<any>): Renderer3;
createRenderer(hostElement: RElement|null, rendererType: RendererType2|null): Renderer3;
begin?(): void;
end?(): void;
}
export const domRendererFactory3: RendererFactory3 = {
createRenderer: (hostElement: RElement | null, rendererType: RendererType2 | null):
Renderer3 => { return document;}
};
/** Subset of API needed for appending elements and text nodes. */
export interface RNode {
removeChild(oldChild: RNode): void;