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

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ function noop() {}
export function main() { export function main() {
let component: LargeTableComponent; let component: LargeTableComponent;
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
component = renderComponent<LargeTableComponent>(LargeTableComponent, {renderer: document}); component = renderComponent<LargeTableComponent>(LargeTableComponent);
bindAction('#createDom', () => createDom(component)); bindAction('#createDom', () => createDom(component));
bindAction('#destroyDom', () => destroyDom(component)); bindAction('#destroyDom', () => destroyDom(component));
bindAction('#updateDomProfile', profile(() => createDom(component), noop, 'update')); bindAction('#updateDomProfile', profile(() => createDom(component), noop, 'update'));

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {C, E, T, V, b, c, defineComponent, detectChanges, e, rC, rc, s, t, v} from '@angular/core/src/render3/index'; import {C, E, T, V, b, c, cR, cr, defineComponent, detectChanges, e, s, t, v} from '@angular/core/src/render3/index';
import {ComponentDef} from '@angular/core/src/render3/public_interfaces'; import {ComponentDef} from '@angular/core/src/render3/public_interfaces';
import {TableCell, buildTable, emptyTable} from '../util'; import {TableCell, buildTable, emptyTable} from '../util';
@ -31,7 +31,7 @@ export class LargeTableComponent {
} }
e(); e();
} }
rC(2); cR(2);
{ {
for (let row of ctx.data) { for (let row of ctx.data) {
let cm1 = V(1); let cm1 = V(1);
@ -42,7 +42,7 @@ export class LargeTableComponent {
c(); c();
e(); e();
} }
rC(1); cR(1);
{ {
for (let cell of row) { for (let cell of row) {
let cm2 = V(2); let cm2 = V(2);
@ -58,12 +58,12 @@ export class LargeTableComponent {
v(); v();
} }
} }
rc(); cr();
} }
v(); v();
} }
} }
rc(); cr();
}, },
factory: () => new LargeTableComponent(), factory: () => new LargeTableComponent(),
inputs: {data: 'data'} inputs: {data: 'data'}

View File

@ -15,7 +15,7 @@ function noop() {}
export function main() { export function main() {
let component: TreeComponent; let component: TreeComponent;
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
component = renderComponent(TreeComponent, {renderer: document}); component = renderComponent(TreeComponent);
bindAction('#createDom', () => createDom(component)); bindAction('#createDom', () => createDom(component));
bindAction('#destroyDom', () => destroyDom(component)); bindAction('#destroyDom', () => destroyDom(component));
bindAction('#detectChanges', () => detectChanges(component)); bindAction('#detectChanges', () => detectChanges(component));

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {C, D, E, T, V, b, b1, c, defineComponent, detectChanges as _detectChanges, e, p, rC, rc, s, t, v} from '@angular/core/src/render3/index'; import {C, D, E, T, V, b, b1, c, cR, cr, defineComponent, detectChanges as _detectChanges, e, p, s, t, v} from '@angular/core/src/render3/index';
import {ComponentDef} from '@angular/core/src/render3/public_interfaces'; import {ComponentDef} from '@angular/core/src/render3/public_interfaces';
import {TreeNode, buildTree, emptyTree} from '../util'; import {TreeNode, buildTree, emptyTree} from '../util';
@ -50,7 +50,7 @@ export class TreeComponent {
} }
s(0, 'background-color', b(ctx.data.depth % 2 ? '' : 'grey')); s(0, 'background-color', b(ctx.data.depth % 2 ? '' : 'grey'));
t(1, b1(' ', ctx.data.value, ' ')); t(1, b1(' ', ctx.data.value, ' '));
rC(2); cR(2);
{ {
if (ctx.data.left != null) { if (ctx.data.left != null) {
let cm0 = V(0); let cm0 = V(0);
@ -66,8 +66,8 @@ export class TreeComponent {
v(); v();
} }
} }
rc(); cr();
rC(3); cR(3);
{ {
if (ctx.data.right != null) { if (ctx.data.right != null) {
let cm0 = V(0); let cm0 = V(0);
@ -83,7 +83,7 @@ export class TreeComponent {
v(); v();
} }
} }
rc(); cr();
}, },
factory: () => new TreeComponent, factory: () => new TreeComponent,
inputs: {data: 'data'} inputs: {data: 'data'}
@ -118,7 +118,7 @@ export function TreeTpl(ctx: TreeNode, cm: boolean) {
} }
s(0, 'background-color', b(ctx.depth % 2 ? '' : 'grey')); s(0, 'background-color', b(ctx.depth % 2 ? '' : 'grey'));
t(1, b1(' ', ctx.value, ' ')); t(1, b1(' ', ctx.value, ' '));
rC(2); cR(2);
{ {
if (ctx.left != null) { if (ctx.left != null) {
let cm0 = V(0); let cm0 = V(0);
@ -126,8 +126,8 @@ export function TreeTpl(ctx: TreeNode, cm: boolean) {
v(); v();
} }
} }
rc(); cr();
rC(3); cR(3);
{ {
if (ctx.right != null) { if (ctx.right != null) {
let cm0 = V(0); let cm0 = V(0);
@ -135,5 +135,5 @@ export function TreeTpl(ctx: TreeNode, cm: boolean) {
v(); v();
} }
} }
rc(); cr();
} }

View File

@ -15,7 +15,7 @@ function noop() {}
export function main() { export function main() {
let component: TreeFunction; let component: TreeFunction;
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
component = renderComponent(TreeFunction, {renderer: document}); component = renderComponent(TreeFunction);
bindAction('#createDom', () => createDom(component)); bindAction('#createDom', () => createDom(component));
bindAction('#destroyDom', () => destroyDom(component)); bindAction('#destroyDom', () => destroyDom(component));
bindAction('#detectChanges', () => detectChanges(component)); bindAction('#detectChanges', () => detectChanges(component));

View File

@ -9,10 +9,10 @@
import {ComponentRef, EmbeddedViewRef, Injector} from '../core'; import {ComponentRef, EmbeddedViewRef, Injector} from '../core';
import {assertNotNull} from './assert'; 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 {LElement} from './l_node';
import {ComponentDef, ComponentType} from './public_interfaces'; import {ComponentDef, ComponentType} from './public_interfaces';
import {RElement, Renderer3, RendererFactory3} from './renderer'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './renderer';
import {notImplemented, stringify} from './util'; import {notImplemented, stringify} from './util';
@ -22,10 +22,8 @@ import {notImplemented, stringify} from './util';
*/ */
export interface CreateComponentOptionArgs { export interface CreateComponentOptionArgs {
/** /**
* Which renderer to use. * Which renderer factory to use.
*/ */
renderer?: Renderer3;
rendererFactory?: RendererFactory3; rendererFactory?: RendererFactory3;
/** /**
@ -138,13 +136,16 @@ export const NULL_INJECTOR: Injector = {
*/ */
export function renderComponent<T>( export function renderComponent<T>(
componentType: ComponentType<T>, opts: CreateComponentOptionArgs = {}): T { componentType: ComponentType<T>, opts: CreateComponentOptionArgs = {}): T {
const renderer = opts.renderer || document; const rendererFactory = opts.rendererFactory || domRendererFactory3;
const componentDef = componentType.ngComponentDef; const componentDef = componentType.ngComponentDef;
let component: T; 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 { try {
// Create element node at index 0 in data array // 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) // Create directive instance with n() and store at index 1 in data array (el is 0)
component = directive(1, componentDef.n(), componentDef); component = directive(1, componentDef.n(), componentDef);
} finally { } finally {
@ -163,15 +164,8 @@ export function detectChanges<T>(component: T) {
createError('Not a directive instance', component); createError('Not a directive instance', component);
} }
ngDevMode && assertNotNull(hostNode.data, 'hostNode.data'); ngDevMode && assertNotNull(hostNode.data, 'hostNode.data');
const oldView = enterView(hostNode.view !, hostNode); renderComponentOrTemplate(hostNode, hostNode.view, component);
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; isDirty = false;
} finally {
leaveView(oldView);
}
} }
let isDirty = false; let isDirty = false;

View File

@ -18,9 +18,9 @@ import {NgStaticData, LNodeStatic, LContainerStatic, InitialInputData, InitialIn
import {assertNodeType} from './node_assert'; import {assertNodeType} from './node_assert';
import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation'; import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation';
import {isNodeMatchingSelector} from './node_selector_matcher'; 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 {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'; import {isDifferent, stringify} from './util';
export {queryRefresh} from './query'; export {queryRefresh} from './query';
@ -73,6 +73,7 @@ let nextNgElementId = 0;
* Renderer2. * Renderer2.
*/ */
let renderer: Renderer3; let renderer: Renderer3;
let rendererFactory: RendererFactory3;
/** Used to set the parent property when nodes are created. */ /** Used to set the parent property when nodes are created. */
let previousOrParentNode: LNode; let previousOrParentNode: LNode;
@ -278,18 +279,44 @@ export function createLNode(
/** /**
* *
* @param host Existing node to render into. * @param host Existing node to render into.
* @param renderer Renderer to use.
* @param template Template function with the instructions. * @param template Template function with the instructions.
* @param context to pass into the template. * @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 !; const hostView = host.data !;
ngDevMode && assertNotEqual(hostView, null, 'hostView'); ngDevMode && assertNotEqual(hostView, null, 'hostView');
hostView.ngStaticData = getTemplateStatic(template); 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 { 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 { } finally {
if (rendererFactory.end) {
rendererFactory.end();
}
leaveView(oldView); leaveView(oldView);
} }
} }
@ -406,7 +433,10 @@ export function elementStart(
let componentView: ViewState|null = null; let componentView: ViewState|null = null;
if (isHostElement) { if (isHostElement) {
const ngStaticData = getTemplateStatic((nameOrComponentDef as ComponentDef<any>).template); 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 // 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. * @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); ngDevMode && assertDataInRange(-1);
rendererFactory = factory;
const defaultRenderer = factory.createRenderer(null, null);
const rNode = typeof elementOrSelector === 'string' ? const rNode = typeof elementOrSelector === 'string' ?
((renderer as ProceduralRenderer3).selectRootElement ? ((defaultRenderer as ProceduralRenderer3).selectRootElement ?
(renderer as ProceduralRenderer3).selectRootElement(elementOrSelector) : (defaultRenderer as ProceduralRenderer3).selectRootElement(elementOrSelector) :
(renderer as ObjectOrientedRenderer3).querySelector !(elementOrSelector)) : (defaultRenderer as ObjectOrientedRenderer3).querySelector !(elementOrSelector)) :
elementOrSelector; elementOrSelector;
if (ngDevMode && !rNode) { if (ngDevMode && !rNode) {
if (typeof elementOrSelector === 'string') { if (typeof elementOrSelector === 'string') {
@ -471,6 +504,15 @@ export function elementHost(elementOrSelector: RElement | string, def: Component
throw createError('Host node is required:', elementOrSelector); 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( createLNode(
0, LNodeFlags.Element, rNode, createViewState(-1, renderer, getTemplateStatic(def.template))); 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 * 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'; import {componentRefresh, diPublic} from './instructions';
@ -108,6 +108,13 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
* NOTE: only used with component directives. * NOTE: only used with component directives.
*/ */
template: ComponentTemplate<T>; template: ComponentTemplate<T>;
/**
* Renderer type data of the component.
*
* NOTE: only used with component directives.
*/
rendererType: RendererType2|null;
} }
export interface DirectiveDefArgs<T> { export interface DirectiveDefArgs<T> {
@ -125,6 +132,7 @@ export interface ComponentDefArgs<T> extends DirectiveDefArgs<T> {
template: ComponentTemplate<T>; template: ComponentTemplate<T>;
refresh?: (this: ComponentDef<T>, directiveIndex: number, elementIndex: number) => void; refresh?: (this: ComponentDef<T>, directiveIndex: number, elementIndex: number) => void;
features?: ComponentDefFeature[]; features?: ComponentDefFeature[];
rendererType?: RendererType2;
} }
export type DirectiveDefFeature = <T>(directiveDef: DirectiveDef<T>) => void; export type DirectiveDefFeature = <T>(directiveDef: DirectiveDef<T>) => void;
@ -156,6 +164,7 @@ export function defineComponent<T>(componentDefinition: ComponentDefArgs<T>): Co
inputs: invertObject(componentDefinition.inputs), inputs: invertObject(componentDefinition.inputs),
outputs: invertObject(componentDefinition.outputs), outputs: invertObject(componentDefinition.outputs),
methods: invertObject(componentDefinition.methods), methods: invertObject(componentDefinition.methods),
rendererType: resolveRendererType2(componentDefinition.rendererType) || null,
}; };
const feature = componentDefinition.features; const feature = componentDefinition.features;
feature && feature.forEach((fn) => fn(def)); feature && feature.forEach((fn) => fn(def));

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * 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 {Observable} from 'rxjs/Observable';
import * as viewEngine from '../core'; import * as viewEngine from '../core';
@ -15,6 +14,7 @@ import {assertNotNull} from './assert';
import {injectElementRefForNode} from './di'; import {injectElementRefForNode} from './di';
import {QueryState} from './interfaces'; import {QueryState} from './interfaces';
import {LContainer, LElement, LNode, LNodeFlags, LView} from './l_node'; 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. * it will be easy to implement such API.
*/ */
import {RendererStyleFlags2} from '../core'; import {RendererStyleFlags2, RendererType2, ViewEncapsulation} from '../core';
import {ComponentDef} from './public_interfaces'; import {ComponentDef} from './public_interfaces';
// TODO: cleanup once the code is merged in angular/angular // TODO: cleanup once the code is merged in angular/angular
@ -68,11 +68,16 @@ export interface ProceduralRenderer3 {
} }
export interface RendererFactory3 { export interface RendererFactory3 {
createRenderer(hostElement: RElement, componentDef: ComponentDef<any>): Renderer3; createRenderer(hostElement: RElement|null, rendererType: RendererType2|null): Renderer3;
begin?(): void; begin?(): void;
end?(): 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. */ /** Subset of API needed for appending elements and text nodes. */
export interface RNode { export interface RNode {
removeChild(oldChild: RNode): void; removeChild(oldChild: RNode): void;

View File

@ -6,8 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {T, b, defineComponent, markDirty, t} from '../../src/render3/index'; import {ViewEncapsulation} from '../../src/core';
import {D, E, T, b, defineComponent, e, markDirty, t} from '../../src/render3/index';
import {createRendererType2} from '../../src/view';
import {getRendererFactory2} from './imported_renderer2';
import {containerEl, renderComponent, requestAnimationFrame} from './render_util'; import {containerEl, renderComponent, requestAnimationFrame} from './render_util';
describe('component', () => { describe('component', () => {
@ -60,3 +63,113 @@ describe('component', () => {
}); });
}); });
// TODO: add tests with Native once tests are run in real browser (domino doesn't support shadow
// root)
describe('encapsulation', () => {
class WrapperComponent {
static ngComponentDef = defineComponent({
type: WrapperComponent,
tag: 'wrapper',
template: function(ctx: WrapperComponent, cm: boolean) {
if (cm) {
E(0, EncapsulatedComponent.ngComponentDef);
{ D(1, EncapsulatedComponent.ngComponentDef.n(), EncapsulatedComponent.ngComponentDef); }
e();
}
EncapsulatedComponent.ngComponentDef.r(1, 0);
},
factory: () => new WrapperComponent,
});
}
class EncapsulatedComponent {
static ngComponentDef = defineComponent({
type: EncapsulatedComponent,
tag: 'encapsulated',
template: function(ctx: EncapsulatedComponent, cm: boolean) {
if (cm) {
T(0, 'foo');
E(1, LeafComponent.ngComponentDef);
{ D(2, LeafComponent.ngComponentDef.n(), LeafComponent.ngComponentDef); }
e();
}
LeafComponent.ngComponentDef.r(2, 1);
},
factory: () => new EncapsulatedComponent,
rendererType:
createRendererType2({encapsulation: ViewEncapsulation.Emulated, styles: [], data: {}}),
});
}
class LeafComponent {
static ngComponentDef = defineComponent({
type: LeafComponent,
tag: 'leaf',
template: function(ctx: LeafComponent, cm: boolean) {
if (cm) {
E(0, 'span');
{ T(1, 'bar'); }
e();
}
},
factory: () => new LeafComponent,
});
}
it('should encapsulate children, but not host nor grand children', () => {
renderComponent(WrapperComponent, getRendererFactory2(document));
expect(containerEl.outerHTML)
.toEqual(
'<div host=""><encapsulated _nghost-c0="">foo<leaf _ngcontent-c0=""><span>bar</span></leaf></encapsulated></div>');
});
it('should encapsulate host', () => {
renderComponent(EncapsulatedComponent, getRendererFactory2(document));
expect(containerEl.outerHTML)
.toEqual(
'<div host="" _nghost-c0="">foo<leaf _ngcontent-c0=""><span>bar</span></leaf></div>');
});
it('should encapsulate host and children with different attributes', () => {
class WrapperComponentWith {
static ngComponentDef = defineComponent({
type: WrapperComponent,
tag: 'wrapper',
template: function(ctx: WrapperComponentWith, cm: boolean) {
if (cm) {
E(0, LeafComponentwith.ngComponentDef);
{ D(1, LeafComponentwith.ngComponentDef.n(), LeafComponentwith.ngComponentDef); }
e();
}
LeafComponentwith.ngComponentDef.r(1, 0);
},
factory: () => new WrapperComponentWith,
rendererType:
createRendererType2({encapsulation: ViewEncapsulation.Emulated, styles: [], data: {}}),
});
}
class LeafComponentwith {
static ngComponentDef = defineComponent({
type: LeafComponentwith,
tag: 'leaf',
template: function(ctx: LeafComponentwith, cm: boolean) {
if (cm) {
E(0, 'span');
{ T(1, 'bar'); }
e();
}
},
factory: () => new LeafComponentwith,
rendererType:
createRendererType2({encapsulation: ViewEncapsulation.Emulated, styles: [], data: {}}),
});
}
renderComponent(WrapperComponentWith, getRendererFactory2(document));
expect(containerEl.outerHTML)
.toEqual(
'<div host="" _nghost-c1=""><leaf _ngcontent-c1="" _nghost-c2=""><span _ngcontent-c2="">bar</span></leaf></div>');
});
});

View File

@ -6,10 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {EventEmitter, NgZone, Renderer2} from '@angular/core'; import {EventEmitter, NgZone, RendererFactory2} from '@angular/core';
import {EventManager, ɵDomEventsPlugin, ɵDomRendererFactory2, ɵDomSharedStylesHost} from '@angular/platform-browser'; import {EventManager, ɵDomEventsPlugin, ɵDomRendererFactory2, ɵDomSharedStylesHost} from '@angular/platform-browser';
// Adapted renderer: it creates a Renderer2 instance and adapts it to Renderer3 // Adapted renderer: it creates a Renderer2 instance and adapts it to Renderer3
// TODO: remove once this code is in angular/angular // TODO: remove once this code is in angular/angular
export class NoopNgZone implements NgZone { export class NoopNgZone implements NgZone {
@ -47,11 +46,9 @@ export class SimpleDomEventsPlugin extends ɵDomEventsPlugin {
} }
} }
export function getRenderer2(document: any): Renderer2 { export function getRendererFactory2(document: any): RendererFactory2 {
const fakeNgZone: NgZone = new NoopNgZone(); const fakeNgZone: NgZone = new NoopNgZone();
const eventManager = const eventManager =
new EventManager([new SimpleDomEventsPlugin(document, fakeNgZone)], fakeNgZone); new EventManager([new SimpleDomEventsPlugin(document, fakeNgZone)], fakeNgZone);
const rendererFactory2 = return new ɵDomRendererFactory2(eventManager, new ɵDomSharedStylesHost(document));
new ɵDomRendererFactory2(eventManager, new ɵDomSharedStylesHost(document));
return rendererFactory2.createRenderer(null, null);
} }

View File

@ -9,19 +9,17 @@
import {ComponentTemplate, ComponentType, PublicFeature, defineComponent, renderComponent as _renderComponent} from '../../src/render3/index'; import {ComponentTemplate, ComponentType, PublicFeature, defineComponent, renderComponent as _renderComponent} from '../../src/render3/index';
import {NG_HOST_SYMBOL, createLNode, createViewState, renderTemplate} from '../../src/render3/instructions'; import {NG_HOST_SYMBOL, createLNode, createViewState, renderTemplate} from '../../src/render3/instructions';
import {LElement, LNodeFlags} from '../../src/render3/l_node'; import {LElement, LNodeFlags} from '../../src/render3/l_node';
import {RElement, RText, Renderer3} from '../../src/render3/renderer'; import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/renderer';
import {getRenderer2} from './imported_renderer2'; import {getRendererFactory2} from './imported_renderer2';
export const document = ((global || window) as any).document; export const document = ((global || window) as any).document;
export let containerEl: HTMLElement = null !; export let containerEl: HTMLElement = null !;
let host: LElement; let host: LElement|null;
let activeRenderer: Renderer3 = const isRenderer2 = process.argv[3] && process.argv[3] === '--r=renderer2';
(typeof process !== 'undefined' && process.argv[3] && process.argv[3] === '--r=renderer2') ?
getRenderer2(document) :
document;
// tslint:disable-next-line:no-console // tslint:disable-next-line:no-console
console.log( console.log(`Running tests with ${!isRenderer2 ? 'document' : 'Renderer2'} renderer...`);
`Running tests with ${activeRenderer === document ? 'document' : 'Renderer2'} renderer...`); const testRendererFactory: RendererFactory3 =
isRenderer2 ? getRendererFactory2(document) : domRendererFactory3;
export const requestAnimationFrame: export const requestAnimationFrame:
{(fn: () => void): void; flush(): void; queue: (() => void)[];} = function(fn: () => void) { {(fn: () => void): void; flush(): void; queue: (() => void)[];} = function(fn: () => void) {
@ -37,20 +35,22 @@ export function resetDOM() {
requestAnimationFrame.queue = []; requestAnimationFrame.queue = [];
containerEl = document.createElement('div'); containerEl = document.createElement('div');
containerEl.setAttribute('host', ''); containerEl.setAttribute('host', '');
host = createLNode( host = null;
null, LNodeFlags.Element, containerEl, createViewState(-1, activeRenderer, null !));
// TODO: assert that the global state is clean (e.g. ngData, previousOrParentNode, etc) // TODO: assert that the global state is clean (e.g. ngData, previousOrParentNode, etc)
} }
export function renderToHtml(template: ComponentTemplate<any>, ctx: any) { export function renderToHtml(
renderTemplate(host, template, ctx); template: ComponentTemplate<any>, ctx: any, providedRendererFactory?: RendererFactory3) {
return toHtml(host.native); host = renderTemplate(
containerEl, template, ctx, providedRendererFactory || testRendererFactory, host);
return toHtml(containerEl);
} }
beforeEach(resetDOM); beforeEach(resetDOM);
export function renderComponent<T>(type: ComponentType<T>): T { export function renderComponent<T>(type: ComponentType<T>, rendererFactory?: RendererFactory3): T {
return _renderComponent(type, {renderer: activeRenderer, host: containerEl}); return _renderComponent(
type, {rendererFactory: rendererFactory || testRendererFactory, host: containerEl});
} }
export function toHtml<T>(componentOrElement: T | RElement): string { export function toHtml<T>(componentOrElement: T | RElement): string {

View File

@ -0,0 +1,106 @@
/**
* @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 {RendererType2} from '@angular/core';
import {D, E, e} from '../../src/render3';
import {T, defineComponent, detectChanges} from '../../src/render3/index';
import {getRendererFactory2} from './imported_renderer2';
import {document, renderComponent, renderToHtml, resetDOM} from './render_util';
describe('renderer factory lifecycle', () => {
let logs: string[] = [];
let rendererFactory = getRendererFactory2(document);
const createRender = rendererFactory.createRenderer;
rendererFactory.createRenderer = (hostElement: any, type: RendererType2 | null) => {
logs.push('create');
return createRender.apply(rendererFactory, [hostElement, type]);
};
rendererFactory.begin = () => logs.push('begin');
rendererFactory.end = () => logs.push('end');
class SomeComponent {
static ngComponentDef = defineComponent({
type: SomeComponent,
tag: 'some-component',
template: function(ctx: SomeComponent, cm: boolean) {
logs.push('component');
if (cm) {
T(0, 'foo');
}
},
factory: () => new SomeComponent
});
}
class SomeComponentWhichThrows {
static ngComponentDef = defineComponent({
type: SomeComponentWhichThrows,
tag: 'some-component-with-Error',
template: function(ctx: SomeComponentWhichThrows, cm: boolean) {
throw(new Error('SomeComponentWhichThrows threw'));
},
factory: () => new SomeComponentWhichThrows
});
}
function Template(ctx: any, cm: boolean) {
logs.push('function');
if (cm) {
T(0, 'bar');
}
}
function TemplateWithComponent(ctx: any, cm: boolean) {
logs.push('function_with_component');
if (cm) {
T(0, 'bar');
E(1, SomeComponent.ngComponentDef);
{ D(2, SomeComponent.ngComponentDef.n(), SomeComponent.ngComponentDef); }
e();
}
SomeComponent.ngComponentDef.r(2, 1);
}
beforeEach(() => { logs = []; });
it('should work with a component', () => {
const component = renderComponent(SomeComponent, rendererFactory);
expect(logs).toEqual(['create', 'create', 'begin', 'component', 'end']);
logs = [];
detectChanges(component);
expect(logs).toEqual(['begin', 'component', 'end']);
});
it('should work with a component which throws', () => {
expect(() => renderComponent(SomeComponentWhichThrows, rendererFactory)).toThrow();
expect(logs).toEqual(['create', 'create', 'begin', 'end']);
});
it('should work with a template', () => {
renderToHtml(Template, {}, rendererFactory);
expect(logs).toEqual(['create', 'begin', 'function', 'end']);
logs = [];
renderToHtml(Template, {});
expect(logs).toEqual(['begin', 'function', 'end']);
});
it('should work with a template which contains a component', () => {
renderToHtml(TemplateWithComponent, {}, rendererFactory);
expect(logs).toEqual(
['create', 'begin', 'function_with_component', 'create', 'component', 'end']);
logs = [];
renderToHtml(TemplateWithComponent, {});
expect(logs).toEqual(['begin', 'function_with_component', 'component', 'end']);
});
});