diff --git a/modules/benchmarks/src/largetable/iv/largetable.js b/modules/benchmarks/src/largetable/iv/largetable.js index 719919cea0..464f04acdf 100644 --- a/modules/benchmarks/src/largetable/iv/largetable.js +++ b/modules/benchmarks/src/largetable/iv/largetable.js @@ -3,7 +3,7 @@ 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 */ -// clang-format off -function $nextRef(){return++refCount}function $el(e,n,r,t){var o={kind:"E",index:n,name:r,ref:t?++refCount:0,children:[],props:NO_PROPS,domNode:null};return e.children[e.children.length]=o,o}function $cg(e,n,r,t){var o=n.children[e];if(o&&o.index===t)return o.childChanges=null,o;var a={kind:"G",index:t,cm:1,props:{},changes:null,childChanges:null,ref:++refCount,children:[],domNode:null,parent:n,$lastChange:refreshCount,$lastRefresh:refreshCount};return r.cm?n.children[n.children.length]=a:(n.children.splice(e,0,a),addChangeInstruction(a,{kind:"CG",node:a,parent:n,position:e,nextSibling:findNextDomSibling(n,e)})),a}function $lg(e,n){addChildChanges(e,n)}function $dg(e,n,r,t){for(var o=n.children[e];o&&o.indexi;i++)retrieveChanges(t[i],n,r);r&&(e.childChanges=null)}if(o&&o.length){for(var a=n.length,i=0;o.length>i;i++,a++)n[a]=o[i];r&&(e.changes=null)}}function addChangeInstruction(e,n){e.changes?e.changes.splice(e.changes.length,0,n):e.changes=[n],e.$lastChange=refreshCount}function addChildChanges(e,n){(e.changes||e.childChanges)&&(n.childChanges?n.childChanges.push(e):n.childChanges=[e],n.$lastChange=refreshCount)}function findNextDomSibling(e,n,r){void 0===r&&(r=!1);var t,o=e.children;if(n+1i;i++)if(a.children[i]===e)return findNextDomSibling(a,i)}return null}function htmlRenderer(e,n,r){return new Renderer(e,n,r)}function processChanges(e,n,r){for(var t=0,o=e;ti;i++)createDomNode(o[i],n,r,t)}function createElementDomNode(e,n,r,t){var o,a,i=e.props,d=e.atts;if(d&&d.xmlns&&(t=d.xmlns),a=t?r.createElementNS(t,e.name):r.createElement(e.name),d)for(var l in d)d.hasOwnProperty(l)&&a.setAttribute(l,d[l]);if(i)if(t&&!t.match(RX_HTML))for(var l in i)i.hasOwnProperty(l)&&a.setAttribute(l,i[l]);else for(var l in i)if(i.hasOwnProperty(l)&&void 0!==(o=i[l]))if(o&&o.$isMap)if("classList"===l){var c=[];d&&(d.class?c.push(d.class):d.className&&c.push(d.className));for(var s in o)o.hasOwnProperty(s)&&"$"!==s[0]&&o[s]&&c.push(s);c.length&&(a.className=c.join(" "))}else"style"===l&&setMapProperties(a.style,o);else o.call?"o"===l[0]&&addEvtListener(a,e,l):a[l]=o;e.domNode=a,n.appendChild(a),processChildNodes(e,a,r,t)}function setMapProperties(e,n){for(var r in n)n.hasOwnProperty(r)&&"$"!==r[0]&&(e[r]=n[r])}function addEvtListener(e,n,r){e.addEventListener(r.substring(2),function(e){n.props[r].call(n,e)})}function removeGroupFromDom(e,n,r){var t,o=e.children,a=e.domNode;if(o){var i=o.length;n&&r&&countGroupDomChildren(e)===a.childNodes.length&&(a.textContent="",n=!1);for(var d=0;i>d;d++)"G"===(t=o[d]).kind?removeGroupFromDom(t,n,!1):"T"!==t.kind&&"E"!==t.kind||n&&t.domNode&&t.domNode.parentNode===a&&a.removeChild(t.domNode)}e.domNode=null}function countGroupDomChildren(e){for(var n,r,t=0,o=e.children,a=o.length,i=0;a>i;i++)"G"===(r=(n=o[i]).kind)?t+=countGroupDomChildren(n):"D"!==r&&t++;return t}function replaceDomNode(e,n,r){if(e!==n&&r.domNode===e&&(r.domNode=n,"G"===r.kind))for(var t=r.children,o=0;t.length>o;o++)replaceDomNode(e,n,t[o])}function getIntParameter(e){return parseInt(getStringParameter(e),10)}function getStringParameter(e){for(var n,r,t=document.querySelectorAll('input[name="'+e+'"]'),o=0;oi;i++)retrieveChanges(t[i],n,r);r&&(e.childChanges=null)}if(o&&o.length){for(var a=n.length,i=0;o.length>i;i++,a++)n[a]=o[i];r&&(e.changes=null)}}function addChangeInstruction(e,n){e.changes?e.changes.splice(e.changes.length,0,n):e.changes=[n],e.$lastChange=refreshCount}function addChildChanges(e,n){(e.changes||e.childChanges)&&(n.childChanges?n.childChanges.push(e):n.childChanges=[e],n.$lastChange=refreshCount)}function findNextDomSibling(e,n,r){void 0===r&&(r=!1);var t,o=e.children;if(n+1i;i++)if(a.children[i]===e)return findNextDomSibling(a,i)}return null}function htmlRenderer(e,n,r){return new Renderer(e,n,r)}function processChanges(e,n,r){for(var t=0,o=e;ti;i++)createDomNode(o[i],n,r,t)}function createElementDomNode(e,n,r,t){var o,a,i=e.props,d=e.atts;if(d&&d.xmlns&&(t=d.xmlns),a=t?r.createElementNS(t,e.name):r.createElement(e.name),d)for(var l in d)d.hasOwnProperty(l)&&a.setAttribute(l,d[l]);if(i)if(t&&!t.match(RX_HTML))for(var l in i)i.hasOwnProperty(l)&&a.setAttribute(l,i[l]);else for(var l in i)if(i.hasOwnProperty(l)&&void 0!==(o=i[l]))if(o&&o.$isMap)if("classList"===l){var c=[];d&&(d.class?c.push(d.class):d.className&&c.push(d.className));for(var s in o)o.hasOwnProperty(s)&&"$"!==s[0]&&o[s]&&c.push(s);c.length&&(a.className=c.join(" "))}else"style"===l&&setMapProperties(a.style,o);else o.call?"o"===l[0]&&addEvtListener(a,e,l):a[l]=o;e.domNode=a,n.appendChild(a),processChildNodes(e,a,r,t)}function setMapProperties(e,n){for(var r in n)n.hasOwnProperty(r)&&"$"!==r[0]&&(e[r]=n[r])}function addEvtListener(e,n,r){e.addEventListener(r.substring(2),function(e){n.props[r].call(n,e)})}function removeGroupFromDom(e,n,r){var t,o=e.children,a=e.domNode;if(o){var i=o.length;n&&r&&countGroupDomChildren(e)===a.childNodes.length&&(a.textContent="",n=!1);for(var d=0;i>d;d++)"G"===(t=o[d]).kind?removeGroupFromDom(t,n,!1):"T"!==t.kind&&"E"!==t.kind||n&&t.domNode&&t.domNode.parentNode===a&&a.removeChild(t.domNode)}e.domNode=null}function countGroupDomChildren(e){for(var n,r,t=0,o=e.children,a=o.length,i=0;a>i;i++)"G"===(r=(n=o[i]).kind)?t+=countGroupDomChildren(n):"D"!==r&&t++;return t}function replaceDomNode(e,n,r){if(e!==n&&r.domNode===e&&(r.domNode=n,"G"===r.kind))for(var t=r.children,o=0;t.length>o;o++)replaceDomNode(e,n,t[o])}function getIntParameter(e){return parseInt(getStringParameter(e),10)}function getStringParameter(e){for(var n,r,t=document.querySelectorAll('input[name="'+e+'"]'),o=0;o(LargeTableComponent, {renderer: document}); + component = renderComponent(LargeTableComponent); bindAction('#createDom', () => createDom(component)); bindAction('#destroyDom', () => destroyDom(component)); bindAction('#updateDomProfile', profile(() => createDom(component), noop, 'update')); diff --git a/modules/benchmarks/src/largetable/render3/table.ts b/modules/benchmarks/src/largetable/render3/table.ts index 701fb517c8..c0a72afcd7 100644 --- a/modules/benchmarks/src/largetable/render3/table.ts +++ b/modules/benchmarks/src/largetable/render3/table.ts @@ -6,7 +6,7 @@ * 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 {TableCell, buildTable, emptyTable} from '../util'; @@ -31,7 +31,7 @@ export class LargeTableComponent { } e(); } - rC(2); + cR(2); { for (let row of ctx.data) { let cm1 = V(1); @@ -42,7 +42,7 @@ export class LargeTableComponent { c(); e(); } - rC(1); + cR(1); { for (let cell of row) { let cm2 = V(2); @@ -58,12 +58,12 @@ export class LargeTableComponent { v(); } } - rc(); + cr(); } v(); } } - rc(); + cr(); }, factory: () => new LargeTableComponent(), inputs: {data: 'data'} diff --git a/modules/benchmarks/src/tree/render3/index.ts b/modules/benchmarks/src/tree/render3/index.ts index fb3be88db1..fd4b719c90 100644 --- a/modules/benchmarks/src/tree/render3/index.ts +++ b/modules/benchmarks/src/tree/render3/index.ts @@ -15,7 +15,7 @@ function noop() {} export function main() { let component: TreeComponent; if (typeof window !== 'undefined') { - component = renderComponent(TreeComponent, {renderer: document}); + component = renderComponent(TreeComponent); bindAction('#createDom', () => createDom(component)); bindAction('#destroyDom', () => destroyDom(component)); bindAction('#detectChanges', () => detectChanges(component)); diff --git a/modules/benchmarks/src/tree/render3/tree.ts b/modules/benchmarks/src/tree/render3/tree.ts index 10ae8fa7ab..a2680c0195 100644 --- a/modules/benchmarks/src/tree/render3/tree.ts +++ b/modules/benchmarks/src/tree/render3/tree.ts @@ -6,7 +6,7 @@ * 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 {TreeNode, buildTree, emptyTree} from '../util'; @@ -50,7 +50,7 @@ export class TreeComponent { } s(0, 'background-color', b(ctx.data.depth % 2 ? '' : 'grey')); t(1, b1(' ', ctx.data.value, ' ')); - rC(2); + cR(2); { if (ctx.data.left != null) { let cm0 = V(0); @@ -66,8 +66,8 @@ export class TreeComponent { v(); } } - rc(); - rC(3); + cr(); + cR(3); { if (ctx.data.right != null) { let cm0 = V(0); @@ -83,7 +83,7 @@ export class TreeComponent { v(); } } - rc(); + cr(); }, factory: () => new TreeComponent, inputs: {data: 'data'} @@ -118,7 +118,7 @@ export function TreeTpl(ctx: TreeNode, cm: boolean) { } s(0, 'background-color', b(ctx.depth % 2 ? '' : 'grey')); t(1, b1(' ', ctx.value, ' ')); - rC(2); + cR(2); { if (ctx.left != null) { let cm0 = V(0); @@ -126,8 +126,8 @@ export function TreeTpl(ctx: TreeNode, cm: boolean) { v(); } } - rc(); - rC(3); + cr(); + cR(3); { if (ctx.right != null) { let cm0 = V(0); @@ -135,5 +135,5 @@ export function TreeTpl(ctx: TreeNode, cm: boolean) { v(); } } - rc(); + cr(); } diff --git a/modules/benchmarks/src/tree/render3_function/index.ts b/modules/benchmarks/src/tree/render3_function/index.ts index ed8ed8619d..28fecff68b 100644 --- a/modules/benchmarks/src/tree/render3_function/index.ts +++ b/modules/benchmarks/src/tree/render3_function/index.ts @@ -15,7 +15,7 @@ function noop() {} export function main() { let component: TreeFunction; if (typeof window !== 'undefined') { - component = renderComponent(TreeFunction, {renderer: document}); + component = renderComponent(TreeFunction); bindAction('#createDom', () => createDom(component)); bindAction('#destroyDom', () => destroyDom(component)); bindAction('#detectChanges', () => detectChanges(component)); diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 5d602a4c24..99ec239277 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -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( componentType: ComponentType, 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(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).ngComponentDef.r(1, 0); - isDirty = false; - } finally { - leaveView(oldView); - } + renderComponentOrTemplate(hostNode, hostNode.view, component); + isDirty = false; } let isDirty = false; diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index be9b2b9046..9bfee04fdf 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -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(host: LElement, template: ComponentTemplate, context: T) { +export function renderTemplate( + hostNode: RElement, template: ComponentTemplate, 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( + node: LElement, viewState: ViewState, componentOrContext: T, template?: ComponentTemplate) { + 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).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).template); - componentView = addToViewTree(createViewState(-1, renderer, ngStaticData)); + componentView = addToViewTree(createViewState( + -1, rendererFactory.createRenderer( + native, (nameOrComponentDef as ComponentDef).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) { +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) { createLNode( 0, LNodeFlags.Element, rNode, createViewState(-1, renderer, getTemplateStatic(def.template))); } diff --git a/packages/core/src/render3/public_interfaces.ts b/packages/core/src/render3/public_interfaces.ts index f379abbc70..6079ccf5ae 100644 --- a/packages/core/src/render3/public_interfaces.ts +++ b/packages/core/src/render3/public_interfaces.ts @@ -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 extends DirectiveDef { * NOTE: only used with component directives. */ template: ComponentTemplate; + + /** + * Renderer type data of the component. + * + * NOTE: only used with component directives. + */ + rendererType: RendererType2|null; } export interface DirectiveDefArgs { @@ -125,6 +132,7 @@ export interface ComponentDefArgs extends DirectiveDefArgs { template: ComponentTemplate; refresh?: (this: ComponentDef, directiveIndex: number, elementIndex: number) => void; features?: ComponentDefFeature[]; + rendererType?: RendererType2; } export type DirectiveDefFeature = (directiveDef: DirectiveDef) => void; @@ -156,6 +164,7 @@ export function defineComponent(componentDefinition: ComponentDefArgs): 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)); diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 11e52c7340..421bc79c52 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -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'; diff --git a/packages/core/src/render3/renderer.ts b/packages/core/src/render3/renderer.ts index de399793fc..aafafa0e4f 100644 --- a/packages/core/src/render3/renderer.ts +++ b/packages/core/src/render3/renderer.ts @@ -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): 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; diff --git a/packages/core/test/render3/component_spec.ts b/packages/core/test/render3/component_spec.ts index 66e04df8c9..e23285c60f 100644 --- a/packages/core/test/render3/component_spec.ts +++ b/packages/core/test/render3/component_spec.ts @@ -6,8 +6,11 @@ * 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'; 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( + '
foobar
'); + }); + + it('should encapsulate host', () => { + renderComponent(EncapsulatedComponent, getRendererFactory2(document)); + expect(containerEl.outerHTML) + .toEqual( + '
foobar
'); + }); + + 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( + '
bar
'); + }); +}); diff --git a/packages/core/test/render3/imported_renderer2.ts b/packages/core/test/render3/imported_renderer2.ts index cff5f6b84c..b2858cceb2 100644 --- a/packages/core/test/render3/imported_renderer2.ts +++ b/packages/core/test/render3/imported_renderer2.ts @@ -6,10 +6,9 @@ * 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'; - // Adapted renderer: it creates a Renderer2 instance and adapts it to Renderer3 // TODO: remove once this code is in angular/angular 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 eventManager = new EventManager([new SimpleDomEventsPlugin(document, fakeNgZone)], fakeNgZone); - const rendererFactory2 = - new ɵDomRendererFactory2(eventManager, new ɵDomSharedStylesHost(document)); - return rendererFactory2.createRenderer(null, null); + return new ɵDomRendererFactory2(eventManager, new ɵDomSharedStylesHost(document)); } diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index 6d7f9ffbfa..88c9c0942c 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -9,19 +9,17 @@ import {ComponentTemplate, ComponentType, PublicFeature, defineComponent, renderComponent as _renderComponent} from '../../src/render3/index'; import {NG_HOST_SYMBOL, createLNode, createViewState, renderTemplate} from '../../src/render3/instructions'; import {LElement, LNodeFlags} from '../../src/render3/l_node'; -import {RElement, RText, Renderer3} from '../../src/render3/renderer'; -import {getRenderer2} from './imported_renderer2'; +import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/renderer'; +import {getRendererFactory2} from './imported_renderer2'; export const document = ((global || window) as any).document; export let containerEl: HTMLElement = null !; -let host: LElement; -let activeRenderer: Renderer3 = - (typeof process !== 'undefined' && process.argv[3] && process.argv[3] === '--r=renderer2') ? - getRenderer2(document) : - document; +let host: LElement|null; +const isRenderer2 = process.argv[3] && process.argv[3] === '--r=renderer2'; // tslint:disable-next-line:no-console -console.log( - `Running tests with ${activeRenderer === document ? 'document' : 'Renderer2'} renderer...`); +console.log(`Running tests with ${!isRenderer2 ? 'document' : 'Renderer2'} renderer...`); +const testRendererFactory: RendererFactory3 = + isRenderer2 ? getRendererFactory2(document) : domRendererFactory3; export const requestAnimationFrame: {(fn: () => void): void; flush(): void; queue: (() => void)[];} = function(fn: () => void) { @@ -37,20 +35,22 @@ export function resetDOM() { requestAnimationFrame.queue = []; containerEl = document.createElement('div'); containerEl.setAttribute('host', ''); - host = createLNode( - null, LNodeFlags.Element, containerEl, createViewState(-1, activeRenderer, null !)); + host = null; // TODO: assert that the global state is clean (e.g. ngData, previousOrParentNode, etc) } -export function renderToHtml(template: ComponentTemplate, ctx: any) { - renderTemplate(host, template, ctx); - return toHtml(host.native); +export function renderToHtml( + template: ComponentTemplate, ctx: any, providedRendererFactory?: RendererFactory3) { + host = renderTemplate( + containerEl, template, ctx, providedRendererFactory || testRendererFactory, host); + return toHtml(containerEl); } beforeEach(resetDOM); -export function renderComponent(type: ComponentType): T { - return _renderComponent(type, {renderer: activeRenderer, host: containerEl}); +export function renderComponent(type: ComponentType, rendererFactory?: RendererFactory3): T { + return _renderComponent( + type, {rendererFactory: rendererFactory || testRendererFactory, host: containerEl}); } export function toHtml(componentOrElement: T | RElement): string { diff --git a/packages/core/test/render3/renderer_factory_spec.ts b/packages/core/test/render3/renderer_factory_spec.ts new file mode 100644 index 0000000000..0efe207c61 --- /dev/null +++ b/packages/core/test/render3/renderer_factory_spec.ts @@ -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']); + }); + +});