diff --git a/modules/@angular/core/src/view/element.ts b/modules/@angular/core/src/view/element.ts index 829c86ef36..7caf65e994 100644 --- a/modules/@angular/core/src/view/element.ts +++ b/modules/@angular/core/src/view/element.ts @@ -9,8 +9,8 @@ import {isDevMode} from '../application_ref'; import {SecurityContext} from '../security'; -import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Refs, ViewData, ViewDefinition, ViewFlags, asElementData} from './types'; -import {checkAndUpdateBinding, dispatchEvent, entryAction, setBindingDebugInfo, setCurrentNode, sliceErrorStack, unwrapValue} from './util'; +import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewFlags, asElementData} from './types'; +import {checkAndUpdateBinding, dispatchEvent, sliceErrorStack, unwrapValue} from './util'; export function anchorDef( flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, @@ -65,7 +65,7 @@ export function elementDef( matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; }); } bindings = bindings || []; - const bindingDefs = new Array(bindings.length); + const bindingDefs: BindingDef[] = new Array(bindings.length); for (let i = 0; i < bindings.length; i++) { const entry = bindings[i]; let bindingDef: BindingDef; @@ -82,7 +82,7 @@ export function elementDef( securityContext = entry[2]; break; } - bindingDefs[i] = {type: bindingType, name, nonMinfiedName: name, securityContext, suffix}; + bindingDefs[i] = {type: bindingType, name, nonMinifiedName: name, securityContext, suffix}; } outputs = outputs || []; const outputDefs: ElementOutputDef[] = new Array(outputs.length); @@ -130,68 +130,30 @@ export function elementDef( export function createElement(view: ViewData, renderHost: any, def: NodeDef): ElementData { const elDef = def.element; - const rootSelectorOrNode = view.root.selectorOrNode; + const rootElement = view.root.element; + const renderer = view.root.renderer; let el: any; - if (view.parent || !rootSelectorOrNode) { + if (view.parent || !rootElement) { const parentNode = def.parent != null ? asElementData(view, def.parent).renderElement : renderHost; - if (view.renderer) { - const debugContext = isDevMode() ? Refs.createDebugContext(view, def.index) : undefined; - el = elDef.name ? view.renderer.createElement(parentNode, elDef.name, debugContext) : - view.renderer.createTemplateAnchor(parentNode, debugContext); - } else { - el = elDef.name ? document.createElement(elDef.name) : document.createComment(''); - if (parentNode) { - parentNode.appendChild(el); - } + el = elDef.name ? renderer.createElement(elDef.name) : renderer.createComment(''); + if (parentNode) { + renderer.appendChild(parentNode, el); } } else { - if (view.renderer) { - const debugContext = isDevMode() ? Refs.createDebugContext(view, def.index) : undefined; - el = view.renderer.selectRootElement(rootSelectorOrNode, debugContext); - } else { - el = typeof rootSelectorOrNode === 'string' ? document.querySelector(rootSelectorOrNode) : - rootSelectorOrNode; - el.textContent = ''; - } + el = rootElement; } if (elDef.attrs) { for (let attrName in elDef.attrs) { - if (view.renderer) { - view.renderer.setElementAttribute(el, attrName, elDef.attrs[attrName]); - } else { - el.setAttribute(attrName, elDef.attrs[attrName]); - } + renderer.setAttribute(el, attrName, elDef.attrs[attrName]); } } if (elDef.outputs.length) { for (let i = 0; i < elDef.outputs.length; i++) { const output = elDef.outputs[i]; - let disposable: DisposableFn; - if (view.renderer) { - const handleEventClosure = renderEventHandlerClosure(view, def.index, output.eventName); - if (output.target) { - disposable = - view.renderer.listenGlobal(output.target, output.eventName, handleEventClosure); - } else { - disposable = view.renderer.listen(el, output.eventName, handleEventClosure); - } - } else { - let target: any; - switch (output.target) { - case 'window': - target = window; - break; - case 'document': - target = document; - break; - default: - target = el; - } - const handleEventClosure = directDomEventHandlerClosure(view, def.index, output.eventName); - target.addEventListener(output.eventName, handleEventClosure); - disposable = target.removeEventListener.bind(target, output.eventName, handleEventClosure); - } + const handleEventClosure = renderEventHandlerClosure(view, def.index, output.eventName); + const disposable = + renderer.listen(output.target || el, output.eventName, handleEventClosure); view.disposables[def.disposableIndex + i] = disposable; } } @@ -203,21 +165,10 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El } function renderEventHandlerClosure(view: ViewData, index: number, eventName: string) { - return entryAction( - EntryAction.HandleEvent, (event: any) => dispatchEvent(view, index, eventName, event)); + return (event: any) => dispatchEvent(view, index, eventName, event); } -function directDomEventHandlerClosure(view: ViewData, index: number, eventName: string) { - return entryAction(EntryAction.HandleEvent, (event: any) => { - const result = dispatchEvent(view, index, eventName, event); - if (result === false) { - event.preventDefault(); - } - return result; - }); -} - export function checkAndUpdateElementInline( view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any, v9: any) { @@ -282,26 +233,20 @@ function setElementAttribute( const securityContext = binding.securityContext; let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value; renderValue = renderValue != null ? renderValue.toString() : null; - if (view.renderer) { - view.renderer.setElementAttribute(renderNode, name, renderValue); + const renderer = view.root.renderer; + if (value != null) { + renderer.setAttribute(renderNode, name, renderValue); } else { - if (value != null) { - renderNode.setAttribute(name, renderValue); - } else { - renderNode.removeAttribute(name); - } + renderer.removeAttribute(renderNode, name); } } function setElementClass(view: ViewData, renderNode: any, name: string, value: boolean) { - if (view.renderer) { - view.renderer.setElementClass(renderNode, name, value); + const renderer = view.root.renderer; + if (value) { + renderer.addClass(renderNode, name); } else { - if (value) { - renderNode.classList.add(name); - } else { - renderNode.classList.remove(name); - } + renderer.removeClass(renderNode, name); } } @@ -317,16 +262,11 @@ function setElementStyle( } else { renderValue = null; } - if (view.renderer) { - view.renderer.setElementStyle(renderNode, name, renderValue); + const renderer = view.root.renderer; + if (renderValue != null) { + renderer.setStyle(renderNode, name, renderValue); } else { - if (renderValue != null) { - renderNode.style[name] = renderValue; - } else { - // IE requires '' instead of null - // see https://github.com/angular/angular/issues/7916 - (renderNode.style as any)[name] = ''; - } + renderer.removeStyle(renderNode, name); } } @@ -334,12 +274,5 @@ function setElementProperty( view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) { const securityContext = binding.securityContext; let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value; - if (view.renderer) { - view.renderer.setElementProperty(renderNode, name, renderValue); - if (isDevMode() && (view.def.flags & ViewFlags.DirectDom) === 0) { - setBindingDebugInfo(view.renderer, renderNode, name, renderValue); - } - } else { - renderNode[name] = renderValue; - } + view.root.renderer.setProperty(renderNode, name, renderValue); } diff --git a/modules/@angular/core/src/view/errors.ts b/modules/@angular/core/src/view/errors.ts index 2d90157434..e4765e219d 100644 --- a/modules/@angular/core/src/view/errors.ts +++ b/modules/@angular/core/src/view/errors.ts @@ -7,12 +7,12 @@ */ import {ERROR_DEBUG_CONTEXT, ERROR_ORIGINAL_ERROR, getDebugContext} from '../errors'; -import {DebugContext, EntryAction, ViewState} from './types'; +import {DebugContext, ViewState} from './types'; export function expressionChangedAfterItHasBeenCheckedError( context: DebugContext, oldValue: any, currValue: any, isFirstCheck: boolean): Error { let msg = - `Expression has changed after it was checked. Previous value: '${oldValue}'. Current value: '${currValue}'.`; + `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '${oldValue}'. Current value: '${currValue}'.`; if (isFirstCheck) { msg += ` It seems like the view has been created after its parent and its children have been dirty checked.` + @@ -39,6 +39,6 @@ export function isViewDebugError(err: Error): boolean { return !!getDebugContext(err); } -export function viewDestroyedError(action: EntryAction): Error { - return new Error(`View has been used after destroy for ${EntryAction[action]}`); +export function viewDestroyedError(action: string): Error { + return new Error(`ViewDestroyedError: Attempt to use a destroyed view: ${action}`); } diff --git a/modules/@angular/core/src/view/index.ts b/modules/@angular/core/src/view/index.ts index 0e2a25ea9a..fdf50b8089 100644 --- a/modules/@angular/core/src/view/index.ts +++ b/modules/@angular/core/src/view/index.ts @@ -11,16 +11,11 @@ export {ngContentDef} from './ng_content'; export {directiveDef, providerDef} from './provider'; export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression'; export {queryDef} from './query'; +export {createComponentFactory} from './refs'; +export {initServicesIfNeeded} from './services'; export {textDef} from './text'; -export {rootRenderNodes, setCurrentNode} from './util'; -export {checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createEmbeddedView, createRootView, destroyView, viewDef} from './view'; +export {rootRenderNodes} from './util'; +export {viewDef} from './view'; export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; + export * from './types'; - -import {createRefs} from './refs'; -import {Refs} from './types'; - -Refs.setInstance(createRefs()); - -export const createComponentFactory: typeof Refs.createComponentFactory = - Refs.createComponentFactory; diff --git a/modules/@angular/core/src/view/ng_content.ts b/modules/@angular/core/src/view/ng_content.ts index c5716c9b25..1edfe5f1e4 100644 --- a/modules/@angular/core/src/view/ng_content.ts +++ b/modules/@angular/core/src/view/ng_content.ts @@ -46,13 +46,6 @@ export function appendNgContent(view: ViewData, renderHost: any, def: NodeDef) { return; } const ngContentIndex = def.ngContent.index; - if (view.renderer) { - const projectedNodes: any[] = []; - visitProjectedRenderNodes( - view, ngContentIndex, RenderNodeAction.Collect, undefined, undefined, projectedNodes); - view.renderer.projectNodes(parentEl, projectedNodes); - } else { - visitProjectedRenderNodes( - view, ngContentIndex, RenderNodeAction.AppendChild, parentEl, undefined, undefined); - } + visitProjectedRenderNodes( + view, ngContentIndex, RenderNodeAction.AppendChild, parentEl, undefined, undefined); } diff --git a/modules/@angular/core/src/view/provider.ts b/modules/@angular/core/src/view/provider.ts index ce62ce6b1d..191e98f245 100644 --- a/modules/@angular/core/src/view/provider.ts +++ b/modules/@angular/core/src/view/provider.ts @@ -6,23 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ -import {isDevMode} from '../application_ref'; import {ChangeDetectorRef, SimpleChange, SimpleChanges} from '../change_detection/change_detection'; import {Injector} from '../di'; -import {stringify} from '../facade/lang'; import {ElementRef} from '../linker/element_ref'; import {TemplateRef} from '../linker/template_ref'; import {ViewContainerRef} from '../linker/view_container_ref'; -import {Renderer} from '../render/api'; +import * as v1renderer from '../render/api'; import {Type} from '../type'; -import {queryDef} from './query'; -import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderOutputDef, ProviderType, QueryBindingType, QueryDef, QueryValueType, Refs, RootData, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types'; -import {checkAndUpdateBinding, dispatchEvent, entryAction, findElementDef, parentDiIndex, setBindingDebugInfo, setCurrentNode, unwrapValue} from './util'; +import {createChangeDetectorRef, createInjector, createTemplateRef, createViewContainerRef} from './refs'; +import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderOutputDef, ProviderType, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types'; +import {checkAndUpdateBinding, dispatchEvent, findElementDef, isComponentView, parentDiIndex, tokenKey, unwrapValue} from './util'; -const _tokenKeyCache = new Map(); - -const RendererTokenKey = tokenKey(Renderer); +const RendererV1TokenKey = tokenKey(v1renderer.Renderer); const ElementRefTokenKey = tokenKey(ElementRef); const ViewContainerRefTokenKey = tokenKey(ViewContainerRef); const TemplateRefTokenKey = tokenKey(TemplateRef); @@ -119,23 +115,13 @@ export function _providerDef( }; } -export function tokenKey(token: any): string { - let key = _tokenKeyCache.get(token); - if (!key) { - key = stringify(token) + '_' + _tokenKeyCache.size; - _tokenKeyCache.set(token, key); - } - return key; -} - export function createProviderInstance(view: ViewData, def: NodeDef): any { const providerDef = def.provider; return def.flags & NodeFlags.LazyProvider ? NOT_CREATED : createInstance(view, def); } function eventHandlerClosure(view: ViewData, index: number, eventName: string) { - return entryAction( - EntryAction.HandleEvent, (event: any) => dispatchEvent(view, index, eventName, event)); + return (event: any) => dispatchEvent(view, index, eventName, event); } export function checkAndUpdateProviderInline( @@ -310,18 +296,24 @@ export function resolveDep( while (view) { const elDef = view.def.nodes[elIndex]; switch (tokenKey) { - case RendererTokenKey: - if (view.renderer) { - return view.renderer; - } else { - return Injector.NULL.get(depDef.token, notFoundValue); + case RendererV1TokenKey: { + let compView = view; + while (compView && !isComponentView(compView)) { + compView = compView.parent; } + const rootRenderer: v1renderer.RootRenderer = + view.root.injector.get(v1renderer.RootRenderer); + + // Note: Don't fill in the styles as they have been installed already! + return rootRenderer.renderComponent(new v1renderer.RenderComponentType( + view.def.component.id, '', 0, view.def.component.encapsulation, [], {})); + } case ElementRefTokenKey: return new ElementRef(asElementData(view, elIndex).renderElement); case ViewContainerRefTokenKey: - return Refs.createViewContainerRef(view, elIndex); + return createViewContainerRef(view, elIndex); case TemplateRefTokenKey: - return Refs.createTemplateRef(view, elDef); + return createTemplateRef(view, elDef); case ChangeDetectorRefTokenKey: let cdView = view; // If we are still checking dependencies on the initial element... @@ -331,10 +323,9 @@ export function resolveDep( cdView = asProviderData(view, requestNodeIndex).componentView; } } - // A ViewRef is also a ChangeDetectorRef - return Refs.createViewRef(cdView); + return createChangeDetectorRef(cdView); case InjectorRefTokenKey: - return Refs.createInjector(view, elIndex); + return createInjector(view, elIndex); default: const providerIndex = elDef.element.providerIndices[tokenKey]; if (providerIndex != null) { @@ -374,12 +365,6 @@ function checkAndUpdateProp( // the user passed in the property name as an object has to `providerDef`, // so Closure Compiler will have renamed the property correctly already. provider[propName] = value; - - if (isDevMode() && (view.def.flags & ViewFlags.DirectDom) === 0) { - setBindingDebugInfo( - view.renderer, asElementData(view, def.parent).renderElement, binding.nonMinifiedName, - value); - } if (change) { changes = changes || {}; changes[binding.nonMinifiedName] = change; @@ -399,7 +384,7 @@ export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: Node const nodeIndex = nodeDef.index; if (nodeDef.flags & lifecycles) { // a leaf - setCurrentNode(view, nodeIndex); + Services.setCurrentNode(view, nodeIndex); callProviderLifecycles(asProviderData(view, nodeIndex).instance, nodeDef.flags & lifecycles); } else if ((nodeDef.childFlags & lifecycles) === 0) { // a parent with leafs diff --git a/modules/@angular/core/src/view/pure_expression.ts b/modules/@angular/core/src/view/pure_expression.ts index 1c7c5ba873..ef5741d201 100644 --- a/modules/@angular/core/src/view/pure_expression.ts +++ b/modules/@angular/core/src/view/pure_expression.ts @@ -6,9 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {resolveDep, tokenKey} from './provider'; -import {BindingDef, BindingType, DepDef, DepFlags, NodeData, NodeDef, NodeType, ProviderData, PureExpressionData, PureExpressionType, ViewData, asPureExpressionData} from './types'; -import {checkAndUpdateBinding, unwrapValue} from './util'; +import {BindingDef, BindingType, DepDef, DepFlags, NodeData, NodeDef, NodeType, ProviderData, PureExpressionData, PureExpressionType, Services, ViewData, asPureExpressionData} from './types'; +import {checkAndUpdateBinding, tokenKey, unwrapValue} from './util'; export function purePipeDef(pipeToken: any, argCount: number): NodeDef { return _pureExpressionDef( @@ -64,7 +63,7 @@ function _pureExpressionDef( export function createPureExpression(view: ViewData, def: NodeDef): PureExpressionData { const pipe = def.pureExpression.pipeDep ? - resolveDep(view, def.index, def.parent, def.pureExpression.pipeDep) : + Services.resolveDep(view, def.index, def.parent, def.pureExpression.pipeDep) : undefined; return {value: undefined, pipe}; } @@ -98,6 +97,7 @@ export function checkAndUpdatePureExpressionInline( if (checkAndUpdateBinding(view, def, 0, v0)) changed = true; } + const data = asPureExpressionData(view, def.index); if (changed) { v0 = unwrapValue(v0); v1 = unwrapValue(v1); @@ -110,7 +110,6 @@ export function checkAndUpdatePureExpressionInline( v8 = unwrapValue(v8); v9 = unwrapValue(v9); - const data = asPureExpressionData(view, def.index); let value: any; switch (def.pureExpression.type) { case PureExpressionType.Array: @@ -202,6 +201,7 @@ export function checkAndUpdatePureExpressionInline( } data.value = value; } + return data.value; } export function checkAndUpdatePureExpressionDynamic(view: ViewData, def: NodeDef, values: any[]) { @@ -214,8 +214,8 @@ export function checkAndUpdatePureExpressionDynamic(view: ViewData, def: NodeDef changed = true; } } + const data = asPureExpressionData(view, def.index); if (changed) { - const data = asPureExpressionData(view, def.index); let value: any; switch (def.pureExpression.type) { case PureExpressionType.Array: @@ -240,4 +240,5 @@ export function checkAndUpdatePureExpressionDynamic(view: ViewData, def: NodeDef } data.value = value; } + return data.value; } diff --git a/modules/@angular/core/src/view/query.ts b/modules/@angular/core/src/view/query.ts index 134032f1e3..4b8f754e3b 100644 --- a/modules/@angular/core/src/view/query.ts +++ b/modules/@angular/core/src/view/query.ts @@ -11,7 +11,8 @@ import {QueryList} from '../linker/query_list'; import {TemplateRef} from '../linker/template_ref'; import {ViewContainerRef} from '../linker/view_container_ref'; -import {NodeDef, NodeFlags, NodeType, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Refs, ViewData, asElementData, asProviderData, asQueryList} from './types'; +import {createTemplateRef, createViewContainerRef} from './refs'; +import {NodeDef, NodeFlags, NodeType, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, asElementData, asProviderData, asQueryList} from './types'; import {declaredViewContainer} from './util'; export function queryDef( @@ -158,10 +159,10 @@ export function getQueryValue(view: ViewData, nodeDef: NodeDef, queryId: string) value = new ElementRef(asElementData(view, nodeDef.index).renderElement); break; case QueryValueType.TemplateRef: - value = Refs.createTemplateRef(view, nodeDef); + value = createTemplateRef(view, nodeDef); break; case QueryValueType.ViewContainerRef: - value = Refs.createViewContainerRef(view, nodeDef.index); + value = createViewContainerRef(view, nodeDef.index); break; case QueryValueType.Provider: value = asProviderData(view, nodeDef.index).instance; diff --git a/modules/@angular/core/src/view/refs.ts b/modules/@angular/core/src/view/refs.ts index 6bb5eee15b..8fe04dc0a7 100644 --- a/modules/@angular/core/src/view/refs.ts +++ b/modules/@angular/core/src/view/refs.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {isDevMode} from '../application_ref'; import {ChangeDetectorRef} from '../change_detection/change_detection'; import {Injectable, Injector} from '../di'; import {ComponentFactory, ComponentRef} from '../linker/component_factory'; @@ -13,67 +14,32 @@ import {ElementRef} from '../linker/element_ref'; import {TemplateRef} from '../linker/template_ref'; import {ViewContainerRef} from '../linker/view_container_ref'; import {EmbeddedViewRef, ViewRef} from '../linker/view_ref'; -import {RenderComponentType, Renderer, RootRenderer} from '../render/api'; +import * as v1renderer from '../render/api'; import {Sanitizer, SecurityContext} from '../security'; import {Type} from '../type'; -import {resolveDep, tokenKey} from './provider'; -import {getQueryValue} from './query'; -import {DebugContext, DepFlags, ElementData, NodeData, NodeDef, NodeType, Refs, RootData, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types'; -import {findElementDef, isComponentView, parentDiIndex, renderNode, resolveViewDefinition, rootRenderNodes} from './util'; -import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view'; -import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; +import {DirectDomRenderer, LegacyRendererAdapter} from './renderer'; +import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types'; +import {findElementDef, isComponentView, parentDiIndex, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey} from './util'; const EMPTY_CONTEXT = new Object(); -export function createRefs() { - return new Refs_(); -} - -export class Refs_ implements Refs { - createComponentFactory(selector: string, viewDefFactory: ViewDefinitionFactory): - ComponentFactory { - return new ComponentFactory_(selector, viewDefFactory); - } - createViewRef(data: ViewData): ViewRef { return new ViewRef_(data); } - createViewContainerRef(view: ViewData, elIndex: number): ViewContainerRef { - return new ViewContainerRef_(view, elIndex); - } - createTemplateRef(parentView: ViewData, def: NodeDef): TemplateRef { - return new TemplateRef_(parentView, def); - } - createInjector(view: ViewData, elIndex: number): Injector { return new Injector_(view, elIndex); } - createDebugContext(view: ViewData, nodeIndex: number): DebugContext { - return new DebugContext_(view, nodeIndex); - } +export function createComponentFactory( + selector: string, componentType: Type, + viewDefFactory: ViewDefinitionFactory): ComponentFactory { + return new ComponentFactory_(selector, componentType, viewDefFactory); } class ComponentFactory_ implements ComponentFactory { /** - * Only needed so that we can implement ComponentFactory + * We are not renaming this field as the old ComponentFactory is using it. * @internal */ _viewClass: any; - private _viewDef: ViewDefinition; - private _componentNodeIndex: number; - - constructor(public selector: string, viewDefFactory: ViewDefinitionFactory) { - const viewDef = this._viewDef = resolveViewDefinition(viewDefFactory); - const len = viewDef.nodes.length; - for (let i = 0; i < len; i++) { - const nodeDef = viewDef.nodes[i]; - if (nodeDef.provider && nodeDef.provider.component) { - this._componentNodeIndex = i; - break; - } - } - if (this._componentNodeIndex == null) { - throw new Error(`Illegal State: Could not find a component in the view definition!`); - } - } - - get componentType(): Type { - return this._viewDef.nodes[this._componentNodeIndex].provider.value; + constructor( + public selector: string, public componentType: Type, + _viewDefFactory: ViewDefinitionFactory) { + this._viewClass = _viewDefFactory; } /** @@ -82,29 +48,28 @@ class ComponentFactory_ implements ComponentFactory { create( injector: Injector, projectableNodes: any[][] = null, rootSelectorOrNode: string|any = null): ComponentRef { - if (!projectableNodes) { - projectableNodes = []; + const viewDef = resolveViewDefinition(this._viewClass); + let componentNodeIndex: number; + const len = viewDef.nodes.length; + for (let i = 0; i < len; i++) { + const nodeDef = viewDef.nodes[i]; + if (nodeDef.provider && nodeDef.provider.component) { + componentNodeIndex = i; + break; + } } - if (!rootSelectorOrNode) { - rootSelectorOrNode = this.selector; + if (componentNodeIndex == null) { + throw new Error(`Illegal State: Could not find a component in the view definition!`); } - const renderer = injector.get(RootRenderer); - const sanitizer = injector.get(Sanitizer); - - const root: RootData = - {injector, projectableNodes, selectorOrNode: rootSelectorOrNode, sanitizer, renderer}; - - const view = createRootView(root, this._viewDef, EMPTY_CONTEXT); - const component = asProviderData(view, this._componentNodeIndex).instance; - return new ComponentRef_(view, component); + const view = Services.createRootView( + injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT); + const component = asProviderData(view, componentNodeIndex).instance; + return new ComponentRef_(view, new ViewRef_(view), component); } } class ComponentRef_ implements ComponentRef { - private _viewRef: ViewRef_; - constructor(private _view: ViewData, private _component: any) { - this._viewRef = new ViewRef_(_view); - } + constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) {} get location(): ElementRef { return new ElementRef(asElementData(this._view, 0).renderElement); } get injector(): Injector { return new Injector_(this._view, 0); } get instance(): any { return this._component; }; @@ -116,6 +81,10 @@ class ComponentRef_ implements ComponentRef { onDestroy(callback: Function): void { this._viewRef.onDestroy(callback); } } +export function createViewContainerRef(view: ViewData, elIndex: number): ViewContainerRef { + return new ViewContainerRef_(view, elIndex); +} + class ViewContainerRef_ implements ViewContainerRef { private _data: ElementData; constructor(private _view: ViewData, private _elIndex: number) { @@ -139,8 +108,8 @@ class ViewContainerRef_ implements ViewContainerRef { clear(): void { const len = this._data.embeddedViews.length; for (let i = len - 1; i >= 0; i--) { - const view = detachEmbeddedView(this._data, i); - destroyView(view); + const view = Services.detachEmbeddedView(this._data, i); + Services.destroyView(view); } } @@ -166,13 +135,13 @@ class ViewContainerRef_ implements ViewContainerRef { insert(viewRef: ViewRef, index?: number): ViewRef { const viewData = (viewRef)._view; - attachEmbeddedView(this._data, index, viewData); + Services.attachEmbeddedView(this._data, index, viewData); return viewRef; } move(viewRef: ViewRef_, currentIndex: number): ViewRef { const previousIndex = this._data.embeddedViews.indexOf(viewRef._view); - moveEmbeddedView(this._data, previousIndex, currentIndex); + Services.moveEmbeddedView(this._data, previousIndex, currentIndex); return viewRef; } @@ -181,17 +150,21 @@ class ViewContainerRef_ implements ViewContainerRef { } remove(index?: number): void { - const viewData = detachEmbeddedView(this._data, index); - destroyView(viewData); + const viewData = Services.detachEmbeddedView(this._data, index); + Services.destroyView(viewData); } detach(index?: number): ViewRef { const view = this.get(index); - detachEmbeddedView(this._data, index); + Services.detachEmbeddedView(this._data, index); return view; } } +export function createChangeDetectorRef(view: ViewData): ChangeDetectorRef { + return new ViewRef_(view); +} + class ViewRef_ implements EmbeddedViewRef { /** @internal */ _view: ViewData; @@ -206,20 +179,24 @@ class ViewRef_ implements EmbeddedViewRef { markForCheck(): void { this.reattach(); } detach(): void { this._view.state &= ~ViewState.ChecksEnabled; } - detectChanges(): void { checkAndUpdateView(this._view); } - checkNoChanges(): void { checkNoChangesView(this._view); } + detectChanges(): void { Services.checkAndUpdateView(this._view); } + checkNoChanges(): void { Services.checkNoChangesView(this._view); } reattach(): void { this._view.state |= ViewState.ChecksEnabled; } onDestroy(callback: Function) { this._view.disposables.push(callback); } - destroy() { destroyView(this._view); } + destroy() { Services.destroyView(this._view); } +} + +export function createTemplateRef(view: ViewData, def: NodeDef): TemplateRef { + return new TemplateRef_(view, def); } class TemplateRef_ implements TemplateRef { constructor(private _parentView: ViewData, private _def: NodeDef) {} createEmbeddedView(context: any): EmbeddedViewRef { - return new ViewRef_(createEmbeddedView(this._parentView, this._def, context)); + return new ViewRef_(Services.createEmbeddedView(this._parentView, this._def, context)); } get elementRef(): ElementRef { @@ -227,91 +204,15 @@ class TemplateRef_ implements TemplateRef { } } +export function createInjector(view: ViewData, elIndex: number): Injector { + return new Injector_(view, elIndex); +} + class Injector_ implements Injector { constructor(private view: ViewData, private elIndex: number) {} get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any { - return resolveDep( + return Services.resolveDep( this.view, undefined, this.elIndex, {flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue); } } - -class DebugContext_ implements DebugContext { - private nodeDef: NodeDef; - private elDef: NodeDef; - constructor(public view: ViewData, public nodeIndex: number) { - if (nodeIndex == null) { - this.nodeIndex = nodeIndex = view.parentIndex; - this.view = view = view.parent; - } - this.nodeDef = view.def.nodes[nodeIndex]; - this.elDef = findElementDef(view, nodeIndex); - } - get injector(): Injector { return new Injector_(this.view, this.elDef.index); } - get component(): any { return this.view.component; } - get providerTokens(): any[] { - const tokens: any[] = []; - if (this.elDef) { - for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) { - const childDef = this.view.def.nodes[i]; - if (childDef.type === NodeType.Provider) { - tokens.push(childDef.provider.token); - } else { - i += childDef.childCount; - } - } - } - return tokens; - } - get references(): {[key: string]: any} { - const references: {[key: string]: any} = {}; - if (this.elDef) { - collectReferences(this.view, this.elDef, references); - - for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) { - const childDef = this.view.def.nodes[i]; - if (childDef.type === NodeType.Provider) { - collectReferences(this.view, childDef, references); - } else { - i += childDef.childCount; - } - } - } - return references; - } - get context(): any { return this.view.context; } - get source(): string { - if (this.nodeDef.type === NodeType.Text) { - return this.nodeDef.text.source; - } else { - return this.elDef.element.source; - } - } - get componentRenderElement() { - const elData = findHostElement(this.view); - return elData ? elData.renderElement : undefined; - } - get renderNode(): any { - let nodeDef = this.nodeDef.type === NodeType.Text ? this.nodeDef : this.elDef; - return renderNode(this.view, nodeDef); - } -} - -function findHostElement(view: ViewData): ElementData { - while (view && !isComponentView(view)) { - view = view.parent; - } - if (view.parent) { - const hostData = asElementData(view.parent, view.parentIndex); - return hostData; - } - return undefined; -} - -function collectReferences(view: ViewData, nodeDef: NodeDef, references: {[key: string]: any}) { - for (let queryId in nodeDef.matchedQueries) { - if (queryId.startsWith('#')) { - references[queryId.slice(1)] = getQueryValue(view, nodeDef, queryId); - } - } -} \ No newline at end of file diff --git a/modules/@angular/core/src/view/renderer.ts b/modules/@angular/core/src/view/renderer.ts new file mode 100644 index 0000000000..86cbf2fa0d --- /dev/null +++ b/modules/@angular/core/src/view/renderer.ts @@ -0,0 +1,136 @@ +/** + * @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 {ViewEncapsulation} from '../metadata/view'; +import * as v1 from '../render/api'; + +import {DebugContext, RendererV2} from './types'; + +export class DirectDomRenderer implements RendererV2 { + createElement(name: string): any { return document.createElement(name); } + createComment(value: string): any { return document.createComment(value); } + createText(value: string): any { return document.createTextNode(value); } + appendChild(parent: any, newChild: any): void { parent.appendChild(newChild); } + insertBefore(parent: any, newChild: any, refChild: any): void { + if (parent) { + parent.insertBefore(newChild, refChild); + } + } + removeChild(parent: any, oldChild: any): void { + if (parent) { + parent.removeChild(oldChild); + } + } + selectRootElement(selectorOrNode: string|any, debugInfo?: DebugContext): any { + let el: any; + if (typeof selectorOrNode === 'string') { + el = document.querySelector(selectorOrNode); + } else { + el = selectorOrNode; + } + el.textContent = ''; + return el; + } + parentNode(node: any): any { return node.parentNode; } + nextSibling(node: any): any { return node.nextSiblibng; } + setAttribute(el: any, name: string, value: string): void { return el.setAttribute(name, value); } + removeAttribute(el: any, name: string): void { el.removeAttribute(name); } + addClass(el: any, name: string): void { el.classList.add(name); } + removeClass(el: any, name: string): void { el.classList.remove(name); } + setStyle(el: any, style: string, value: any): void { el.style[style] = value; } + removeStyle(el: any, style: string): void { + // IE requires '' instead of null + // see https://github.com/angular/angular/issues/7916 + (el.style as any)[style] = ''; + } + setProperty(el: any, name: string, value: any): void { el[name] = value; } + setText(node: any, value: string): void { node.nodeValue = value; } + listen(target: any, eventName: string, callback: (event: any) => boolean): () => void { + let renderTarget: any; + switch (target) { + case 'window': + renderTarget = window; + break; + case 'document': + renderTarget = document; + break; + default: + renderTarget = target; + } + const closure = (event: any) => { + if (callback(event) === false) { + event.preventDefault(); + } + }; + renderTarget.addEventListener(eventName, closure); + return () => renderTarget.removeEventListener(eventName, closure); + } +} + +const EMPTY_V1_RENDER_COMPONENT_TYPE = + new v1.RenderComponentType('EMPTY', '', 0, ViewEncapsulation.None, [], {}); + +/** + * A temporal implementation of `Renderer` until we migrated our current renderer + * in all packages to the new API. + * + * Note that this is not complete, e.g. does not support shadow dom, view encapsulation, ...! + */ +export class LegacyRendererAdapter implements RendererV2 { + private _delegate: v1.Renderer; + constructor(rootDelegate: v1.RootRenderer) { + this._delegate = rootDelegate.renderComponent(EMPTY_V1_RENDER_COMPONENT_TYPE); + } + createElement(name: string, debugInfo?: DebugContext): any { + return this._delegate.createElement(null, name, debugInfo); + } + createComment(value: string, debugInfo?: DebugContext): any { + return this._delegate.createTemplateAnchor(null, debugInfo); + } + createText(value: string, debugInfo?: DebugContext): any { + return this._delegate.createText(null, value, debugInfo); + } + appendChild(parent: any, newChild: any): void { this._delegate.projectNodes(parent, [newChild]); } + insertBefore(parent: any, newChild: any, refChild: any): void { + const beforeSibling = refChild.nextSiblingOf ? refChild.nextSiblingOf : refChild; + this._delegate.attachViewAfter(beforeSibling, [newChild]); + } + removeChild(parent: any, oldChild: any): void { + if (parent) { + this._delegate.detachView([oldChild]); + } + } + selectRootElement(selectorOrNode: any, debugInfo?: DebugContext): any { + return this._delegate.selectRootElement(selectorOrNode, debugInfo); + } + parentNode(node: any): any { return {parentOf: node}; } + nextSibling(node: any): any { return {nextSiblingOf: node}; } + setAttribute(el: any, name: string, value: string): void { + this._delegate.setElementAttribute(el, name, value); + } + removeAttribute(el: any, name: string): void { + this._delegate.setElementAttribute(el, name, null); + } + addClass(el: any, name: string): void { this._delegate.setElementClass(el, name, true); } + removeClass(el: any, name: string): void { this._delegate.setElementClass(el, name, false); } + setStyle(el: any, style: string, value: any): void { + this._delegate.setElementStyle(el, style, value); + } + removeStyle(el: any, style: string): void { this._delegate.setElementStyle(el, style, null); } + setProperty(el: any, name: string, value: any): void { + this._delegate.setElementProperty(el, name, value); + } + setText(node: any, value: string): void { this._delegate.setText(node, value); } + listen(target: any, eventName: string, callback: (event: any) => boolean): () => void { + if (typeof target === 'string') { + return this._delegate.listenGlobal(target, eventName, callback); + } else { + return this._delegate.listen(target, eventName, callback); + } + } +} diff --git a/modules/@angular/core/src/view/services.ts b/modules/@angular/core/src/view/services.ts new file mode 100644 index 0000000000..1e9c36ed30 --- /dev/null +++ b/modules/@angular/core/src/view/services.ts @@ -0,0 +1,360 @@ +/** + * @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 {isDevMode} from '../application_ref'; +import {Injectable, Injector} from '../di'; +import {looseIdentical} from '../facade/lang'; +import {ElementRef} from '../linker/element_ref'; +import * as v1renderer from '../render/api'; +import {Sanitizer, SecurityContext} from '../security'; +import {Type} from '../type'; + +import {isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors'; +import {resolveDep} from './provider'; +import {getQueryValue} from './query'; +import {createInjector} from './refs'; +import {DirectDomRenderer, LegacyRendererAdapter} from './renderer'; +import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types'; +import {checkBinding, findElementDef, isComponentView, parentDiIndex, renderNode, resolveViewDefinition, rootRenderNodes} from './util'; +import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view'; +import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; + +let initialized = false; + +export function initServicesIfNeeded() { + if (initialized) { + return; + } + initialized = true; + const services = isDevMode() ? createDebugServices() : createProdServices(); + Services.setCurrentNode = services.setCurrentNode; + Services.createRootView = services.createRootView; + Services.createEmbeddedView = services.createEmbeddedView; + Services.checkAndUpdateView = services.checkAndUpdateView; + Services.checkNoChangesView = services.checkNoChangesView; + Services.destroyView = services.destroyView; + Services.attachEmbeddedView = services.attachEmbeddedView, + Services.detachEmbeddedView = services.detachEmbeddedView, + Services.moveEmbeddedView = services.moveEmbeddedView; + Services.resolveDep = services.resolveDep; + Services.createDebugContext = services.createDebugContext; + Services.handleEvent = services.handleEvent; + Services.updateView = services.updateView; +} + +function createProdServices() { + return { + setCurrentNode: () => {}, + createRootView: createProdRootView, + createEmbeddedView: createEmbeddedView, + checkAndUpdateView: checkAndUpdateView, + checkNoChangesView: checkNoChangesView, + destroyView: destroyView, + attachEmbeddedView: attachEmbeddedView, + detachEmbeddedView: detachEmbeddedView, + moveEmbeddedView: moveEmbeddedView, + resolveDep: resolveDep, + createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex), + handleEvent: (view: ViewData, nodeIndex: number, eventName: string, event: any) => + view.def.handleEvent(view, nodeIndex, eventName, event), + updateView: (check: NodeCheckFn, view: ViewData) => view.def.update(check, view) + }; +} + +function createDebugServices() { + return { + setCurrentNode: debugSetCurrentNode, + createRootView: debugCreateRootView, + createEmbeddedView: debugCreateEmbeddedView, + checkAndUpdateView: debugCheckAndUpdateView, + checkNoChangesView: debugCheckNoChangesView, + destroyView: debugDestroyView, + attachEmbeddedView: attachEmbeddedView, + detachEmbeddedView: detachEmbeddedView, + moveEmbeddedView: moveEmbeddedView, + resolveDep: resolveDep, + createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex), + handleEvent: debugHandleEvent, + updateView: debugUpdateView + }; +} + +function createProdRootView( + injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string | any, + def: ViewDefinition, context?: any): ViewData { + return createRootView( + createRootData(injector, projectableNodes, rootSelectorOrNode), def, context); +} + +function debugCreateRootView( + injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string | any, + def: ViewDefinition, context?: any): ViewData { + const root = createRootData(injector, projectableNodes, rootSelectorOrNode); + const debugRoot: RootData = { + injector: root.injector, + projectableNodes: root.projectableNodes, + element: root.element, + renderer: new DebugRenderer(root.renderer), + sanitizer: root.sanitizer + }; + return callWithDebugContext('create', createRootView, null, [debugRoot, def, context]); +} + +function createRootData( + injector: Injector, projectableNodes: any[][], rootSelectorOrNode: any): RootData { + const sanitizer = injector.get(Sanitizer); + // TODO(tbosch): once the new renderer interface is implemented via platform-browser, + // just get it via the injector and drop LegacyRendererAdapter and DirectDomRenderer. + const renderer = isDevMode() ? new LegacyRendererAdapter(injector.get(v1renderer.RootRenderer)) : + new DirectDomRenderer(); + const rootElement = + rootSelectorOrNode ? renderer.selectRootElement(rootSelectorOrNode) : undefined; + return {injector, projectableNodes, element: rootElement, sanitizer, renderer}; +} + +function debugCreateEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData { + return callWithDebugContext('create', createEmbeddedView, null, [parent, anchorDef, context]); +} + +function debugCheckAndUpdateView(view: ViewData) { + return callWithDebugContext('detectChanges', checkAndUpdateView, null, [view]); +} + +function debugCheckNoChangesView(view: ViewData) { + return callWithDebugContext('checkNoChanges', checkNoChangesView, null, [view]); +} + +function debugDestroyView(view: ViewData) { + return callWithDebugContext('destroyView', destroyView, null, [view]); +} + + +let _currentAction: string; +let _currentView: ViewData; +let _currentNodeIndex: number; + +function debugSetCurrentNode(view: ViewData, nodeIndex: number) { + _currentView = view; + _currentNodeIndex = nodeIndex; +} + +function debugHandleEvent(view: ViewData, nodeIndex: number, eventName: string, event: any) { + if (view.state & ViewState.Destroyed) { + throw viewDestroyedError(_currentAction); + } + debugSetCurrentNode(view, nodeIndex); + return callWithDebugContext( + 'handleEvent', view.def.handleEvent, null, [view, nodeIndex, eventName, event]); +} + +function debugUpdateView(check: NodeCheckFn, view: ViewData) { + if (view.state & ViewState.Destroyed) { + throw viewDestroyedError(_currentAction); + } + debugSetCurrentNode(view, nextNodeIndexWithBinding(view, 0)); + return view.def.update(debugCheckFn, view); + + function debugCheckFn( + view: ViewData, nodeIndex: number, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any, + v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any) { + const values = argStyle === ArgumentType.Dynamic ? v0 : [].slice.call(arguments, 3); + const nodeDef = view.def.nodes[nodeIndex]; + for (let i = 0; i < nodeDef.bindings.length; i++) { + const binding = nodeDef.bindings[i]; + const value = values[i]; + if ((binding.type === BindingType.ElementProperty || + binding.type === BindingType.ProviderProperty) && + checkBinding(view, nodeDef, i, value)) { + const elIndex = nodeDef.type === NodeType.Provider ? nodeDef.parent : nodeDef.index; + setBindingDebugInfo( + view.root.renderer, asElementData(view, elIndex).renderElement, binding.nonMinifiedName, + value); + } + } + const result = check(view, nodeIndex, argStyle, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); + + debugSetCurrentNode(view, nextNodeIndexWithBinding(view, nodeIndex)); + return result; + }; +} + +function setBindingDebugInfo(renderer: RendererV2, renderNode: any, propName: string, value: any) { + try { + renderer.setAttribute( + renderNode, `ng-reflect-${camelCaseToDashCase(propName)}`, value ? value.toString() : null); + } catch (e) { + renderer.setAttribute( + renderNode, `ng-reflect-${camelCaseToDashCase(propName)}`, + '[ERROR] Exception while trying to serialize the value'); + } +} + +const CAMEL_CASE_REGEXP = /([A-Z])/g; + +function camelCaseToDashCase(input: string): string { + return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase()); +} + +function nextNodeIndexWithBinding(view: ViewData, nodeIndex: number): number { + for (let i = nodeIndex; i < view.def.nodes.length; i++) { + const nodeDef = view.def.nodes[i]; + if (nodeDef.bindings && nodeDef.bindings.length) { + return i; + } + } + return undefined; +} + + +class DebugRenderer implements RendererV2 { + constructor(private _delegate: RendererV2) {} + createElement(name: string): any { + return this._delegate.createElement(name, getCurrentDebugContext()); + } + createComment(value: string): any { + return this._delegate.createComment(value, getCurrentDebugContext()); + } + createText(value: string): any { + return this._delegate.createText(value, getCurrentDebugContext()); + } + appendChild(parent: any, newChild: any): void { + return this._delegate.appendChild(parent, newChild); + } + insertBefore(parent: any, newChild: any, refChild: any): void { + return this._delegate.insertBefore(parent, newChild, refChild); + } + removeChild(parent: any, oldChild: any): void { + return this._delegate.removeChild(parent, oldChild); + } + selectRootElement(selectorOrNode: string|any): any { + return this._delegate.selectRootElement(selectorOrNode, getCurrentDebugContext()); + } + parentNode(node: any): any { return this._delegate.parentNode(node); } + nextSibling(node: any): any { return this._delegate.nextSibling(node); } + setAttribute(el: any, name: string, value: string): void { + return this._delegate.setAttribute(el, name, value); + } + removeAttribute(el: any, name: string): void { return this._delegate.removeAttribute(el, name); } + addClass(el: any, name: string): void { return this._delegate.addClass(el, name); } + removeClass(el: any, name: string): void { return this._delegate.removeClass(el, name); } + setStyle(el: any, style: string, value: any): void { + return this._delegate.setStyle(el, style, value); + } + removeStyle(el: any, style: string): void { return this._delegate.removeStyle(el, style); } + setProperty(el: any, name: string, value: any): void { + return this._delegate.setProperty(el, name, value); + } + setText(node: any, value: string): void { return this._delegate.setText(node, value); } + listen(target: 'window'|'document'|any, eventName: string, callback: (event: any) => boolean): + () => void { + return this._delegate.listen(target, eventName, callback); + } +} + +class DebugContext_ implements DebugContext { + private nodeDef: NodeDef; + private elDef: NodeDef; + constructor(public view: ViewData, public nodeIndex: number) { + if (nodeIndex == null) { + this.nodeIndex = nodeIndex = view.parentIndex; + this.view = view = view.parent; + } + this.nodeDef = view.def.nodes[nodeIndex]; + this.elDef = findElementDef(view, nodeIndex); + } + get injector(): Injector { return createInjector(this.view, this.elDef.index); } + get component(): any { return this.view.component; } + get providerTokens(): any[] { + const tokens: any[] = []; + if (this.elDef) { + for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) { + const childDef = this.view.def.nodes[i]; + if (childDef.type === NodeType.Provider) { + tokens.push(childDef.provider.token); + } else { + i += childDef.childCount; + } + } + } + return tokens; + } + get references(): {[key: string]: any} { + const references: {[key: string]: any} = {}; + if (this.elDef) { + collectReferences(this.view, this.elDef, references); + + for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) { + const childDef = this.view.def.nodes[i]; + if (childDef.type === NodeType.Provider) { + collectReferences(this.view, childDef, references); + } else { + i += childDef.childCount; + } + } + } + return references; + } + get context(): any { return this.view.context; } + get source(): string { + if (this.nodeDef.type === NodeType.Text) { + return this.nodeDef.text.source; + } else { + return this.elDef.element.source; + } + } + get componentRenderElement() { + const elData = findHostElement(this.view); + return elData ? elData.renderElement : undefined; + } + get renderNode(): any { + let nodeDef = this.nodeDef.type === NodeType.Text ? this.nodeDef : this.elDef; + return renderNode(this.view, nodeDef); + } +} + +function findHostElement(view: ViewData): ElementData { + while (view && !isComponentView(view)) { + view = view.parent; + } + if (view.parent) { + const hostData = asElementData(view.parent, view.parentIndex); + return hostData; + } + return undefined; +} + +function collectReferences(view: ViewData, nodeDef: NodeDef, references: {[key: string]: any}) { + for (let queryId in nodeDef.matchedQueries) { + if (queryId.startsWith('#')) { + references[queryId.slice(1)] = getQueryValue(view, nodeDef, queryId); + } + } +} + +function callWithDebugContext(action: string, fn: any, self: any, args: any[]) { + const oldAction = _currentAction; + const oldView = _currentView; + const oldNodeIndex = _currentNodeIndex; + try { + _currentAction = action; + const result = fn.apply(self, args); + _currentView = oldView; + _currentNodeIndex = oldNodeIndex; + _currentAction = oldAction; + return result; + } catch (e) { + if (isViewDebugError(e) || !_currentView) { + throw e; + } + throw viewWrappedDebugError(e, getCurrentDebugContext()); + } +} + +function getCurrentDebugContext() { + return new DebugContext_(_currentView, _currentNodeIndex); +} \ No newline at end of file diff --git a/modules/@angular/core/src/view/text.ts b/modules/@angular/core/src/view/text.ts index ef4cb526df..5697f411c0 100644 --- a/modules/@angular/core/src/view/text.ts +++ b/modules/@angular/core/src/view/text.ts @@ -9,7 +9,7 @@ import {isDevMode} from '../application_ref'; import {looseIdentical} from '../facade/lang'; -import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, Refs, RootData, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types'; +import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types'; import {checkAndUpdateBinding, sliceErrorStack, unwrapValue} from './util'; export function textDef(ngContentIndex: number, constants: string[]): NodeDef { @@ -53,14 +53,10 @@ export function createText(view: ViewData, renderHost: any, def: NodeDef): TextD const parentNode = def.parent != null ? asElementData(view, def.parent).renderElement : renderHost; let renderNode: any; - if (view.renderer) { - const debugContext = isDevMode() ? Refs.createDebugContext(view, def.index) : undefined; - renderNode = view.renderer.createText(parentNode, def.text.prefix, debugContext); - } else { - renderNode = document.createTextNode(def.text.prefix); - if (parentNode) { - parentNode.appendChild(renderNode); - } + const renderer = view.root.renderer; + renderNode = renderer.createText(def.text.prefix); + if (parentNode) { + renderer.appendChild(parentNode, renderNode); } return {renderText: renderNode}; } @@ -121,11 +117,7 @@ export function checkAndUpdateTextInline( } value = def.text.prefix + value; const renderNode = asTextData(view, def.index).renderText; - if (view.renderer) { - view.renderer.setText(renderNode, value); - } else { - renderNode.nodeValue = value; - } + view.root.renderer.setText(renderNode, value); } } @@ -146,11 +138,7 @@ export function checkAndUpdateTextDynamic(view: ViewData, def: NodeDef, values: } value = def.text.prefix + value; const renderNode = asTextData(view, def.index).renderText; - if (view.renderer) { - view.renderer.setText(renderNode, value); - } else { - renderNode.nodeValue = value; - } + view.root.renderer.setText(renderNode, value); } } diff --git a/modules/@angular/core/src/view/types.ts b/modules/@angular/core/src/view/types.ts index f8faf32c16..03f640a58a 100644 --- a/modules/@angular/core/src/view/types.ts +++ b/modules/@angular/core/src/view/types.ts @@ -8,12 +8,12 @@ import {PipeTransform} from '../change_detection/change_detection'; import {Injector} from '../di'; -import {ComponentFactory} from '../linker/component_factory'; +import {ComponentRef} from '../linker/component_factory'; import {QueryList} from '../linker/query_list'; import {TemplateRef} from '../linker/template_ref'; import {ViewContainerRef} from '../linker/view_container_ref'; import {ViewRef} from '../linker/view_ref'; -import {RenderComponentType, RenderDebugInfo, Renderer, RootRenderer} from '../render/api'; +import {ViewEncapsulation} from '../metadata/view'; import {Sanitizer, SecurityContext} from '../security'; // ------------------------------------- @@ -22,7 +22,7 @@ import {Sanitizer, SecurityContext} from '../security'; export interface ViewDefinition { flags: ViewFlags; - componentType: RenderComponentType; + component: ComponentDefinition; update: ViewUpdateFn; handleEvent: ViewHandleEventFn; /** @@ -49,10 +49,24 @@ export interface ViewDefinition { export type ViewDefinitionFactory = () => ViewDefinition; -export type ViewUpdateFn = (view: ViewData) => void; +export type ViewUpdateFn = (check: NodeCheckFn, view: ViewData) => void; -export type ViewHandleEventFn = - (view: ViewData, nodeIndex: number, eventName: string, event: any) => boolean; +// helper functions to create an overloaded function type. +export declare function _nodeCheckFn( + view: ViewData, nodeIndex: number, argStyle: ArgumentType.Dynamic, values: any[]): any; + export declare function _nodeCheckFn( + view: ViewData, nodeIndex: number, argStyle: ArgumentType.Inline, v0?: any, v1?: any, + v2?: any, v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): + any; + + export type NodeCheckFn = typeof _nodeCheckFn; + + export type ViewHandleEventFn = + (view: ViewData, nodeIndex: number, eventName: string, event: any) => boolean; + + export enum ArgumentType { + Inline, Dynamic + } /** * Bitmask for ViewDefintion.flags. @@ -63,6 +77,12 @@ export enum ViewFlags { OnPush = 1 << 2 } +export interface ComponentDefinition { + id: string; + encapsulation: ViewEncapsulation; + styles: string[]; +} + /** * A node definition in the view. * @@ -265,7 +285,6 @@ export interface NgContentDef { */ export interface ViewData { def: ViewDefinition; - renderer: Renderer; root: RootData; // index of parent element / anchor. Not the index // of the provider with the component view. @@ -389,59 +408,103 @@ export function asQueryList(view: ViewData, index: number): QueryList { export interface RootData { injector: Injector; projectableNodes: any[][]; - selectorOrNode: string|any; - renderer: RootRenderer; + element: any; + renderer: RendererV2; sanitizer: Sanitizer; } +/** + * TODO(tbosch): move this interface into @angular/core/src/render/api, + * and implement it in @angular/platform-browser, ... + */ +export interface RendererV2 { + createElement(name: string, debugInfo?: RenderDebugContext): any; + createComment(value: string, debugInfo?: RenderDebugContext): any; + createText(value: string, debugInfo?: RenderDebugContext): any; + appendChild(parent: any, newChild: any): void; + insertBefore(parent: any, newChild: any, refChild: any): void; + removeChild(parent: any, oldChild: any): void; + selectRootElement(selectorOrNode: string|any, debugInfo?: RenderDebugContext): any; + /** + * Attention: On WebWorkers, this will always return a value, + * as we are asking for a result synchronously. I.e. + * the caller can't rely on checking whether this is null or not. + */ + parentNode(node: any): any; + /** + * Attention: On WebWorkers, this will always return a value, + * as we are asking for a result synchronously. I.e. + * the caller can't rely on checking whether this is null or not. + */ + nextSibling(node: any): any; + setAttribute(el: any, name: string, value: string): void; + removeAttribute(el: any, name: string): void; + addClass(el: any, name: string): void; + removeClass(el: any, name: string): void; + setStyle(el: any, style: string, value: any): void; + removeStyle(el: any, style: string): void; + setProperty(el: any, name: string, value: any): void; + setText(node: any, value: string): void; + listen(target: 'window'|'document'|any, eventName: string, callback: (event: any) => boolean): + () => void; +} + +export abstract class RenderDebugContext { + abstract get injector(): Injector; + abstract get component(): any; + abstract get providerTokens(): any[]; + abstract get references(): {[key: string]: any}; + abstract get context(): any; + abstract get source(): string; + abstract get componentRenderElement(): any; + abstract get renderNode(): any; +} + +export abstract class DebugContext extends RenderDebugContext { + abstract get view(): ViewData; + abstract get nodeIndex(): number; +} + // ------------------------------------- // Other // ------------------------------------- -export enum EntryAction { - CheckAndUpdate, - CheckNoChanges, - Create, - Destroy, - HandleEvent -} -export interface DebugContext extends RenderDebugInfo { - view: ViewData; - nodeIndex: number; - componentRenderElement: any; - renderNode: any; +export interface Services { + setCurrentNode(view: ViewData, nodeIndex: number): void; + createRootView( + injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string|any, + def: ViewDefinition, context?: any): ViewData; + createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData; + checkAndUpdateView(view: ViewData): void; + checkNoChangesView(view: ViewData): void; + attachEmbeddedView(elementData: ElementData, viewIndex: number, view: ViewData): void; + detachEmbeddedView(elementData: ElementData, viewIndex: number): ViewData; + moveEmbeddedView(elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData; + destroyView(view: ViewData): void; + resolveDep( + view: ViewData, requestNodeIndex: number, elIndex: number, depDef: DepDef, + notFoundValue?: any): any; + createDebugContext(view: ViewData, nodeIndex: number): DebugContext; + handleEvent: ViewHandleEventFn; + updateView: ViewUpdateFn; } /** - * This class is used to prevent cycles in the source files. + * This object is used to prevent cycles in the source files and to have a place where + * debug mode can hook it. It is lazily filled when `isDevMode` is known. */ -export abstract class Refs { - private static instance: Refs; - - static setInstance(instance: Refs) { Refs.instance = instance; } - static createComponentFactory(selector: string, viewDefFactory: ViewDefinitionFactory): - ComponentFactory { - return Refs.instance.createComponentFactory(selector, viewDefFactory); - } - static createViewRef(data: ViewData): ViewRef { return Refs.instance.createViewRef(data); } - static createViewContainerRef(view: ViewData, elIndex: number): ViewContainerRef { - return Refs.instance.createViewContainerRef(view, elIndex); - } - static createTemplateRef(parentView: ViewData, def: NodeDef): TemplateRef { - return Refs.instance.createTemplateRef(parentView, def); - } - static createInjector(view: ViewData, elIndex: number): Injector { - return Refs.instance.createInjector(view, elIndex); - } - static createDebugContext(view: ViewData, nodeIndex: number): DebugContext { - return Refs.instance.createDebugContext(view, nodeIndex); - } - - abstract createComponentFactory(selector: string, viewDefFactory: ViewDefinitionFactory): - ComponentFactory; - abstract createViewRef(data: ViewData): ViewRef; - abstract createViewContainerRef(view: ViewData, elIndex: number): ViewContainerRef; - abstract createTemplateRef(parentView: ViewData, def: NodeDef): TemplateRef; - abstract createInjector(view: ViewData, elIndex: number): Injector; - abstract createDebugContext(view: ViewData, nodeIndex: number): DebugContext; -} +export const Services: Services = { + setCurrentNode: undefined, + createRootView: undefined, + createEmbeddedView: undefined, + checkAndUpdateView: undefined, + checkNoChangesView: undefined, + destroyView: undefined, + attachEmbeddedView: undefined, + detachEmbeddedView: undefined, + moveEmbeddedView: undefined, + resolveDep: undefined, + createDebugContext: undefined, + handleEvent: undefined, + updateView: undefined, +}; diff --git a/modules/@angular/core/src/view/util.ts b/modules/@angular/core/src/view/util.ts index 6a42c6e34b..eff2bf90a2 100644 --- a/modules/@angular/core/src/view/util.ts +++ b/modules/@angular/core/src/view/util.ts @@ -10,31 +10,30 @@ import {isDevMode} from '../application_ref'; import {WrappedValue, devModeEqual} from '../change_detection/change_detection'; import {SimpleChange} from '../change_detection/change_detection_util'; import {Injector} from '../di'; -import {looseIdentical} from '../facade/lang'; +import {looseIdentical, stringify} from '../facade/lang'; import {TemplateRef} from '../linker/template_ref'; import {ViewContainerRef} from '../linker/view_container_ref'; import {ViewRef} from '../linker/view_ref'; import {Renderer} from '../render/api'; import {expressionChangedAfterItHasBeenCheckedError, isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors'; -import {DebugContext, ElementData, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, Refs, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types'; +import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, NodeType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types'; -export function setBindingDebugInfo( - renderer: Renderer, renderNode: any, propName: string, value: any) { - try { - renderer.setBindingDebugInfo( - renderNode, `ng-reflect-${camelCaseToDashCase(propName)}`, value ? value.toString() : null); - } catch (e) { - renderer.setBindingDebugInfo( - renderNode, `ng-reflect-${camelCaseToDashCase(propName)}`, - '[ERROR] Exception while trying to serialize the value'); +const _tokenKeyCache = new Map(); + +export function tokenKey(token: any): string { + let key = _tokenKeyCache.get(token); + if (!key) { + key = stringify(token) + '_' + _tokenKeyCache.size; + _tokenKeyCache.set(token, key); } + return key; } -const CAMEL_CASE_REGEXP = /([A-Z])/g; - -function camelCaseToDashCase(input: string): string { - return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase()); +export function checkBinding( + view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean { + const oldValue = view.oldValues[def.bindingIndex + bindingIdx]; + return !!(view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value); } export function checkBindingNoChanges( @@ -42,7 +41,7 @@ export function checkBindingNoChanges( const oldValue = view.oldValues[def.bindingIndex + bindingIdx]; if ((view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value)) { throw expressionChangedAfterItHasBeenCheckedError( - Refs.createDebugContext(view, def.index), oldValue, value, + Services.createDebugContext(view, def.index), oldValue, value, (view.state & ViewState.FirstCheck) !== 0); } } @@ -66,7 +65,6 @@ export function checkAndUpdateBinding( export function dispatchEvent( view: ViewData, nodeIndex: number, eventName: string, event: any): boolean { - setCurrentNode(view, nodeIndex); let currView = view; while (currView) { if (currView.def.flags & ViewFlags.OnPush) { @@ -74,7 +72,7 @@ export function dispatchEvent( } currView = currView.parent; } - return view.def.handleEvent(view, nodeIndex, eventName, event); + return Services.handleEvent(view, nodeIndex, eventName, event); } export function unwrapValue(value: any): any { @@ -159,68 +157,6 @@ export function sliceErrorStack(start: number, end: number): string { return lines.slice(start, end).join('\n'); } -let _currentAction: EntryAction; -let _currentView: ViewData; -let _currentNodeIndex: number; - -export function currentView() { - return _currentView; -} - -export function currentNodeIndex() { - return _currentNodeIndex; -} - -export function currentAction() { - return _currentAction; -} - -/** - * Set the node that is currently worked on. - * It needs to be called whenever we call user code, - * or code of the framework that might throw as a valid use case. - */ -export function setCurrentNode(view: ViewData, nodeIndex: number) { - if (view.state & ViewState.Destroyed) { - throw viewDestroyedError(_currentAction); - } - _currentView = view; - _currentNodeIndex = nodeIndex; -} - -/** - * Adds a try/catch handler around the given function to wrap all - * errors that occur into new errors that contain the current debug info - * set via setCurrentNode. - */ -export function entryAction(action: EntryAction, fn: (arg: A) => R): (arg: A) => R { - return function(arg: any) { - const oldAction = _currentAction; - const oldView = _currentView; - const oldNodeIndex = _currentNodeIndex; - _currentAction = action; - // Note: We can't call `isDevMode()` outside of this closure as - // it might not have been initialized. - const result = isDevMode() ? callWithTryCatch(fn, arg) : fn(arg); - _currentAction = oldAction; - _currentView = oldView; - _currentNodeIndex = oldNodeIndex; - return result; - }; -} - -function callWithTryCatch(fn: (a: any) => any, arg: any): any { - try { - return fn(arg); - } catch (e) { - if (isViewDebugError(e) || !_currentView) { - throw e; - } - const debugContext = Refs.createDebugContext(_currentView, _currentNodeIndex); - throw viewWrappedDebugError(e, debugContext); - } -} - export function rootRenderNodes(view: ViewData): any[] { const renderNodes: any[] = []; visitRootRenderNodes(view, RenderNodeAction.Collect, undefined, undefined, renderNodes); @@ -269,7 +205,7 @@ export function visitProjectedRenderNodes( const projectedNodes = view.root.projectableNodes[ngContentIndex]; if (projectedNodes) { for (let i = 0; i < projectedNodes.length; i++) { - execRenderNodeAction(projectedNodes[i], action, parentNode, nextSibling, target); + execRenderNodeAction(view, projectedNodes[i], action, parentNode, nextSibling, target); } } } @@ -283,7 +219,7 @@ function visitRenderNode( view, nodeDef.ngContent.index, action, parentNode, nextSibling, target); } else { const rn = renderNode(view, nodeDef); - execRenderNodeAction(rn, action, parentNode, nextSibling, target); + execRenderNodeAction(view, rn, action, parentNode, nextSibling, target); if (nodeDef.flags & NodeFlags.HasEmbeddedViews) { const embeddedViews = asElementData(view, nodeDef.index).embeddedViews; if (embeddedViews) { @@ -296,16 +232,18 @@ function visitRenderNode( } function execRenderNodeAction( - renderNode: any, action: RenderNodeAction, parentNode: any, nextSibling: any, target: any[]) { + view: ViewData, renderNode: any, action: RenderNodeAction, parentNode: any, nextSibling: any, + target: any[]) { + const renderer = view.root.renderer; switch (action) { case RenderNodeAction.AppendChild: - parentNode.appendChild(renderNode); + renderer.appendChild(parentNode, renderNode); break; case RenderNodeAction.InsertBefore: - parentNode.insertBefore(renderNode, nextSibling); + renderer.insertBefore(parentNode, renderNode, nextSibling); break; case RenderNodeAction.RemoveChild: - parentNode.removeChild(renderNode); + renderer.removeChild(parentNode, renderNode); break; case RenderNodeAction.Collect: target.push(renderNode); diff --git a/modules/@angular/core/src/view/view.ts b/modules/@angular/core/src/view/view.ts index 6cd98ba57b..35ebc2562b 100644 --- a/modules/@angular/core/src/view/view.ts +++ b/modules/@angular/core/src/view/view.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {RenderComponentType, Renderer} from '../render/api'; +import {ViewEncapsulation} from '../metadata/view'; import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element'; import {expressionChangedAfterItHasBeenCheckedError} from './errors'; import {appendNgContent} from './ng_content'; @@ -14,14 +14,15 @@ import {callLifecycleHooksChildrenFirst, checkAndUpdateProviderDynamic, checkAnd import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression'; import {checkAndUpdateQuery, createQuery, queryDef} from './query'; import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text'; -import {ElementDef, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, Refs, RootData, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types'; -import {checkBindingNoChanges, currentAction, currentNodeIndex, currentView, entryAction, isComponentView, resolveViewDefinition, setCurrentNode} from './util'; +import {ArgumentType, ComponentDefinition, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types'; +import {checkBindingNoChanges, isComponentView, resolveViewDefinition} from './util'; const NOOP = (): any => undefined; export function viewDef( flags: ViewFlags, nodesWithoutIndices: NodeDef[], update?: ViewUpdateFn, - handleEvent?: ViewHandleEventFn, componentType?: RenderComponentType): ViewDefinition { + handleEvent?: ViewHandleEventFn, compId?: string, encapsulation?: ViewEncapsulation, + styles?: string[]): ViewDefinition { // clone nodes and set auto calculated values if (nodesWithoutIndices.length === 0) { throw new Error(`Illegal State: Views without nodes are not allowed!`); @@ -99,13 +100,15 @@ export function viewDef( } currentParent = newParent; } - + const componentDef = + compId ? {id: compId, encapsulation, styles} : undefined; return { nodeFlags: viewNodeFlags, nodeMatchedQueries: viewMatchedQueries, flags, nodes: nodes, reverseChildNodes, update: update || NOOP, - handleEvent: handleEvent || NOOP, componentType, + handleEvent: handleEvent || NOOP, + component: componentDef, bindingCount: viewBindingCount, disposableCount: viewDisposableCount, lastRootNode }; @@ -196,27 +199,44 @@ function cloneAndModifyNode(nodeDef: NodeDef, values: { bindingIndex: number, disposableIndex: number, }): NodeDef { - const clonedNode: NodeDef = {}; - copyInto(nodeDef, clonedNode); - - clonedNode.index = values.index; - clonedNode.bindingIndex = values.bindingIndex; - clonedNode.disposableIndex = values.disposableIndex; - clonedNode.parent = values.parent; - clonedNode.reverseChildIndex = values.reverseChildIndex; - - // Note: We can't set the value immediately, as we need to walk the children first. - clonedNode.childFlags = 0; - clonedNode.childMatchedQueries = {}; - return clonedNode; + // Attention: don't use copyInto here to prevent v8 from treating this object + // as a dictionary! + return { + type: nodeDef.type, + index: values.index, + reverseChildIndex: values.reverseChildIndex, + parent: values.parent, + childFlags: 0, + childMatchedQueries: {}, + bindingIndex: values.bindingIndex, + disposableIndex: values.disposableIndex, + flags: nodeDef.flags, + matchedQueries: nodeDef.matchedQueries, + ngContentIndex: nodeDef.ngContentIndex, + childCount: nodeDef.childCount, + bindings: nodeDef.bindings, + disposableCount: nodeDef.disposableCount, + element: nodeDef.element, + provider: nodeDef.provider, + text: nodeDef.text, + pureExpression: nodeDef.pureExpression, + query: nodeDef.query, + ngContent: nodeDef.ngContent + }; } function cloneAndModifyElement( elementDef: ElementDef, values: {providerIndices: {[tokenKey: string]: number}}): ElementDef { - const clonedElement: ElementDef = {}; - copyInto(elementDef, clonedElement); - clonedElement.providerIndices = values.providerIndices; - return clonedElement; + // Attention: don't use copyInto here to prevent v8 from treating this object + // as a dictionary! + return { + name: elementDef.name, + attrs: elementDef.attrs, + outputs: elementDef.outputs, + template: elementDef.template, + providerIndices: values.providerIndices, + source: elementDef.source + }; } export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData { @@ -238,13 +258,6 @@ export function createRootView(root: RootData, def: ViewDefinition, context?: an function createView( root: RootData, parent: ViewData, parentIndex: number, def: ViewDefinition): ViewData { const nodes: NodeData[] = new Array(def.nodes.length); - let renderer: Renderer; - if (def.flags != null && (def.flags & ViewFlags.DirectDom)) { - renderer = null; - } else { - renderer = - def.componentType ? root.renderer.renderComponent(def.componentType) : parent.renderer; - } const disposables = def.disposableCount ? new Array(def.disposableCount) : undefined; const view: ViewData = { def, @@ -252,7 +265,7 @@ function createView( parentIndex, context: undefined, component: undefined, nodes, - state: ViewState.FirstCheck | ViewState.ChecksEnabled, renderer, root, + state: ViewState.FirstCheck | ViewState.ChecksEnabled, root, oldValues: new Array(def.bindingCount), disposables }; return view; @@ -263,25 +276,17 @@ function initView(view: ViewData, component: any, context: any) { view.context = context; } -const createViewNodes: (view: ViewData) => void = - entryAction(EntryAction.CheckNoChanges, _createViewNodes); - -function _createViewNodes(view: ViewData) { +function createViewNodes(view: ViewData) { let renderHost: any; if (isComponentView(view)) { renderHost = asElementData(view.parent, view.parentIndex).renderElement; - if (view.renderer) { - renderHost = view.renderer.createViewRoot(renderHost); - } } const def = view.def; const nodes = view.nodes; for (let i = 0; i < def.nodes.length; i++) { const nodeDef = def.nodes[i]; - // As the current node is being created, we have to use - // the parent node as the current node for error messages, ... - setCurrentNode(view, nodeDef.parent); + Services.setCurrentNode(view, i); switch (nodeDef.type) { case NodeType.Element: nodes[i] = createElement(view, renderHost, nodeDef) as any; @@ -324,22 +329,16 @@ function _createViewNodes(view: ViewData) { execComponentViewsAction(view, ViewAction.CreateViewNodes); } -export const checkNoChangesView: (view: ViewData) => void = - entryAction(EntryAction.CheckNoChanges, _checkNoChangesView); - -function _checkNoChangesView(view: ViewData) { - view.def.update(view); +export function checkNoChangesView(view: ViewData) { + Services.updateView(checkNoChangesNode, view); execEmbeddedViewsAction(view, ViewAction.CheckNoChanges); execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckNoChanges); execComponentViewsAction(view, ViewAction.CheckNoChanges); execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckNoChanges); } -export const checkAndUpdateView: (view: ViewData) => void = - entryAction(EntryAction.CheckAndUpdate, _checkAndUpdateView); - -function _checkAndUpdateView(view: ViewData) { - view.def.update(view); +export function checkAndUpdateView(view: ViewData) { + Services.updateView(checkAndUpdateNode, view); execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate); execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckAndUpdate); @@ -359,73 +358,58 @@ function _checkAndUpdateView(view: ViewData) { view.state &= ~ViewState.FirstCheck; } -export function checkNodeInline( - v0?: any, v1?: any, v2?: any, v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, - v9?: any): any { - const action = currentAction(); - const view = currentView(); - const nodeIndex = currentNodeIndex(); - const nodeDef = view.def.nodes[nodeIndex]; - switch (action) { - case EntryAction.CheckNoChanges: - checkNodeNoChangesInline(view, nodeIndex, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); - break; - case EntryAction.CheckAndUpdate: - switch (nodeDef.type) { - case NodeType.Element: - checkAndUpdateElementInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); - break; - case NodeType.Text: - checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); - break; - case NodeType.Provider: - checkAndUpdateProviderInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); - break; - case NodeType.PureExpression: - checkAndUpdatePureExpressionInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); - break; - } - break; - default: - throw new Error(`Illegal State: In action ${EntryAction[action]}`); +function checkAndUpdateNode( + view: ViewData, nodeIndex: number, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any, + v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): any { + if (argStyle === ArgumentType.Inline) { + return checkAndUpdateNodeInline(view, nodeIndex, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); + } else { + return checkAndUpdateNodeDynamic(view, nodeIndex, v0); } - return nodeDef.type === NodeType.PureExpression ? asPureExpressionData(view, nodeIndex).value : - undefined; } -export function checkNodeDynamic(values: any[]): any { - const action = currentAction(); - const view = currentView(); - const nodeIndex = currentNodeIndex(); +function checkAndUpdateNodeInline( + view: ViewData, nodeIndex: number, v0?: any, v1?: any, v2?: any, v3?: any, v4?: any, v5?: any, + v6?: any, v7?: any, v8?: any, v9?: any): any { const nodeDef = view.def.nodes[nodeIndex]; - switch (action) { - case EntryAction.CheckNoChanges: - checkNodeNoChangesDynamic(view, nodeIndex, values); - break; - case EntryAction.CheckAndUpdate: - switch (nodeDef.type) { - case NodeType.Element: - checkAndUpdateElementDynamic(view, nodeDef, values); - break; - case NodeType.Text: - checkAndUpdateTextDynamic(view, nodeDef, values); - break; - case NodeType.Provider: - checkAndUpdateProviderDynamic(view, nodeDef, values); - break; - case NodeType.PureExpression: - checkAndUpdatePureExpressionDynamic(view, nodeDef, values); - break; - } - break; - default: - throw new Error(`Illegal State: In action ${EntryAction[action]}`); + switch (nodeDef.type) { + case NodeType.Element: + return checkAndUpdateElementInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); + case NodeType.Text: + return checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); + case NodeType.Provider: + return checkAndUpdateProviderInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); + case NodeType.PureExpression: + return checkAndUpdatePureExpressionInline( + view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); } - return nodeDef.type === NodeType.PureExpression ? asPureExpressionData(view, nodeIndex).value : - undefined; } -function checkNodeNoChangesInline( +function checkAndUpdateNodeDynamic(view: ViewData, nodeIndex: number, values: any[]): any { + const nodeDef = view.def.nodes[nodeIndex]; + switch (nodeDef.type) { + case NodeType.Element: + return checkAndUpdateElementDynamic(view, nodeDef, values); + case NodeType.Text: + return checkAndUpdateTextDynamic(view, nodeDef, values); + case NodeType.Provider: + return checkAndUpdateProviderDynamic(view, nodeDef, values); + case NodeType.PureExpression: + return checkAndUpdatePureExpressionDynamic(view, nodeDef, values); + } +} + +function checkNoChangesNode( + view: ViewData, nodeIndex: number, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any, + v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): any { + if (argStyle === ArgumentType.Inline) { + return checkNoChangesNodeInline(view, nodeIndex, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); + } else { + return checkNoChangesNodeDynamic(view, nodeIndex, v0); + } +} + +function checkNoChangesNodeInline( view: ViewData, nodeIndex: number, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any, v9: any): void { const nodeDef = view.def.nodes[nodeIndex]; @@ -452,28 +436,29 @@ function checkNodeNoChangesInline( case 1: checkBindingNoChanges(view, nodeDef, 0, v0); } - return undefined; + return nodeDef.type === NodeType.PureExpression ? asPureExpressionData(view, nodeIndex).value : + undefined; } -function checkNodeNoChangesDynamic(view: ViewData, nodeIndex: number, values: any[]): void { +function checkNoChangesNodeDynamic(view: ViewData, nodeIndex: number, values: any[]): void { const nodeDef = view.def.nodes[nodeIndex]; for (let i = 0; i < values.length; i++) { checkBindingNoChanges(view, nodeDef, i, values[i]); } + return nodeDef.type === NodeType.PureExpression ? asPureExpressionData(view, nodeIndex).value : + undefined; } function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) { const queryList = asQueryList(view, nodeDef.index); if (queryList.dirty) { throw expressionChangedAfterItHasBeenCheckedError( - Refs.createDebugContext(view, nodeDef.index), `Query ${nodeDef.query.id} not dirty`, + Services.createDebugContext(view, nodeDef.index), `Query ${nodeDef.query.id} not dirty`, `Query ${nodeDef.query.id} dirty`, (view.state & ViewState.FirstCheck) !== 0); } } -export const destroyView: (view: ViewData) => void = entryAction(EntryAction.Destroy, _destroyView); - -function _destroyView(view: ViewData) { +export function destroyView(view: ViewData) { callLifecycleHooksChildrenFirst(view, NodeFlags.OnDestroy); if (view.disposables) { for (let i = 0; i < view.disposables.length; i++) { @@ -542,20 +527,20 @@ function callViewAction(view: ViewData, action: ViewAction) { case ViewAction.CheckNoChanges: if ((viewState & ViewState.ChecksEnabled) && (viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) { - _checkNoChangesView(view); + checkNoChangesView(view); } break; case ViewAction.CheckAndUpdate: if ((viewState & ViewState.ChecksEnabled) && (viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) { - _checkAndUpdateView(view); + checkAndUpdateView(view); } break; case ViewAction.Destroy: - _destroyView(view); + destroyView(view); break; case ViewAction.CreateViewNodes: - _createViewNodes(view); + createViewNodes(view); break; } } @@ -573,7 +558,7 @@ function execQueriesAction(view: ViewData, queryFlags: NodeFlags, action: QueryA for (let i = 0; i < nodeCount; i++) { const nodeDef = view.def.nodes[i]; if (nodeDef.flags & queryFlags) { - setCurrentNode(view, nodeDef.index); + Services.setCurrentNode(view, nodeDef.index); switch (action) { case QueryAction.CheckAndUpdate: checkAndUpdateQuery(view, nodeDef); diff --git a/modules/@angular/core/src/view/view_attach.ts b/modules/@angular/core/src/view/view_attach.ts index 996f5d8270..354be573c1 100644 --- a/modules/@angular/core/src/view/view_attach.ts +++ b/modules/@angular/core/src/view/view_attach.ts @@ -83,27 +83,16 @@ export function moveEmbeddedView( function renderAttachEmbeddedView(elementData: ElementData, prevView: ViewData, view: ViewData) { const prevRenderNode = prevView ? renderNode(prevView, prevView.def.lastRootNode) : elementData.renderElement; - if (view.renderer) { - view.renderer.attachViewAfter(prevRenderNode, rootRenderNodes(view)); - } else { - const parentNode = prevRenderNode.parentNode; - const nextSibling = prevRenderNode.nextSibling; - if (parentNode) { - const action = nextSibling ? RenderNodeAction.InsertBefore : RenderNodeAction.AppendChild; - visitRootRenderNodes(view, action, parentNode, nextSibling, undefined); - } - } + const parentNode = view.root.renderer.parentNode(prevRenderNode); + const nextSibling = view.root.renderer.nextSibling(prevRenderNode); + // Note: We can't check if `nextSibling` is present, as on WebWorkers it will always be! + // However, browsers automatically do `appendChild` when there is no `nextSibling`. + visitRootRenderNodes(view, RenderNodeAction.InsertBefore, parentNode, nextSibling, undefined); } function renderDetachEmbeddedView(elementData: ElementData, view: ViewData) { - if (view.renderer) { - view.renderer.detachView(rootRenderNodes(view)); - } else { - const parentNode = elementData.renderElement.parentNode; - if (parentNode) { - visitRootRenderNodes(view, RenderNodeAction.RemoveChild, parentNode, null, undefined); - } - } + const parentNode = view.root.renderer.parentNode(elementData.renderElement); + visitRootRenderNodes(view, RenderNodeAction.RemoveChild, parentNode, null, undefined); } function addToArray(arr: any[], index: number, value: any) { diff --git a/modules/@angular/core/test/view/anchor_spec.ts b/modules/@angular/core/test/view/anchor_spec.ts index 3624f4a6c5..c6f82d33df 100644 --- a/modules/@angular/core/test/view/anchor_spec.ts +++ b/modules/@angular/core/test/view/anchor_spec.ts @@ -7,40 +7,22 @@ */ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, getDebugNode} from '@angular/core'; -import {DebugContext, NodeDef, NodeFlags, RootData, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, elementDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {createRootData, isBrowser, setupAndCheckRenderer} from './helper'; +import {createRootView, isBrowser} from './helper'; export function main() { - if (isBrowser()) { - defineTests({directDom: true, viewFlags: ViewFlags.DirectDom}); - } - defineTests({directDom: false, viewFlags: 0}); -} - -function defineTests(config: {directDom: boolean, viewFlags: number}) { - describe(`View Anchor, directDom: ${config.directDom}`, () => { - setupAndCheckRenderer(config); - - let rootData: RootData; - let renderComponentType: RenderComponentType; - - beforeEach(() => { - rootData = createRootData(); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - }); - + describe(`View Anchor`, () => { function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); + return viewDef(ViewFlags.None, nodes, update, handleEvent); } function createAndGetRootNodes( viewDef: ViewDefinition, ctx?: any): {rootNodes: any[], view: ViewData} { - const view = createRootView(rootData, viewDef, ctx); + const view = createRootView(viewDef, ctx); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -69,14 +51,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(getDOM().childNodes(rootNodes[0]).length).toBe(1); }); - if (!config.directDom) { - it('should add debug information to the renderer', () => { - const someContext = new Object(); - const {view, rootNodes} = createAndGetRootNodes( - compViewDef([anchorDef(NodeFlags.None, null, null, 0)]), someContext); - expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asElementData(view, 0).renderElement); - }); - } + it('should add debug information to the renderer', () => { + const someContext = new Object(); + const {view, rootNodes} = createAndGetRootNodes( + compViewDef([anchorDef(NodeFlags.None, null, null, 0)]), someContext); + expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asElementData(view, 0).renderElement); + }); }); }); } diff --git a/modules/@angular/core/test/view/component_view_spec.ts b/modules/@angular/core/test/view/component_view_spec.ts index 44470b0ed9..7f9eeb9d77 100644 --- a/modules/@angular/core/test/view/component_view_spec.ts +++ b/modules/@angular/core/test/view/component_view_spec.ts @@ -7,40 +7,22 @@ */ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {BindingType, NodeDef, NodeFlags, RootData, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, anchorDef, asProviderData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, destroyView, directiveDef, elementDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {ArgumentType, BindingType, NodeCheckFn, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, anchorDef, asProviderData, directiveDef, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {createRootData, isBrowser, removeNodes, setupAndCheckRenderer} from './helper'; +import {createRootView, isBrowser, removeNodes} from './helper'; export function main() { - if (isBrowser()) { - defineTests({directDom: true, viewFlags: ViewFlags.DirectDom}); - } - defineTests({directDom: false, viewFlags: 0}); -} - -function defineTests(config: {directDom: boolean, viewFlags: number}) { - describe(`Component Views, directDom: ${config.directDom}`, () => { - setupAndCheckRenderer(config); - - let rootData: RootData; - let renderComponentType: RenderComponentType; - - beforeEach(() => { - rootData = createRootData(); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - }); - + describe(`Component Views`, () => { function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, - flags?: ViewFlags): ViewDefinition { - return viewDef(config.viewFlags | flags, nodes, update, handleEvent, renderComponentType); + viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, update, handleEvent); } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { - const view = createRootView(rootData, viewDef); + const view = createRootView(viewDef); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -79,39 +61,41 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { }); it('should select root elements based on a selector', () => { - rootData.selectorOrNode = 'root'; - const view = createRootView(rootData, compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - ])); + const view = createRootView( + compViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div'), + ]), + {}, [], 'root'); const rootNodes = rootRenderNodes(view); expect(rootNodes).toEqual([rootNode]); }); it('should select root elements based on a node', () => { - rootData.selectorOrNode = rootNode; - const view = createRootView(rootData, compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - ])); + const view = createRootView( + compViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div'), + ]), + {}, [], rootNode); const rootNodes = rootRenderNodes(view); expect(rootNodes).toEqual([rootNode]); }); it('should set attributes on the root node', () => { - rootData.selectorOrNode = rootNode; - const view = - createRootView(rootData, compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div', {'a': 'b'}), - ])); + const view = createRootView( + compViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div', {'a': 'b'}), + ]), + {}, [], rootNode); expect(rootNode.getAttribute('a')).toBe('b'); }); it('should clear the content of the root node', () => { - rootData.selectorOrNode = rootNode; rootNode.appendChild(document.createElement('div')); - const view = - createRootView(rootData, compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div', {'a': 'b'}), - ])); + const view = createRootView( + compViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div', {'a': 'b'}), + ]), + {}, [], rootNode); expect(rootNode.childNodes.length).toBe(0); }); }); @@ -124,10 +108,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { a: any; } - const update = jasmine.createSpy('updater').and.callFake((view: ViewData) => { - setCurrentNode(view, 0); - checkNodeInline(value); - }); + const update = + jasmine.createSpy('updater').and.callFake((check: NodeCheckFn, view: ViewData) => { + check(view, 0, ArgumentType.Inline, value); + }); const {view, rootNodes} = createAndGetRootNodes( compViewDef([ @@ -141,19 +125,19 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { const compView = asProviderData(view, 1).componentView; value = 'v1'; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); - expect(update).toHaveBeenCalledWith(compView); + expect(update.calls.mostRecent().args[1]).toBe(compView); update.calls.reset(); - checkNoChangesView(view); + Services.checkNoChangesView(view); - expect(update).toHaveBeenCalledWith(compView); + expect(update.calls.mostRecent().args[1]).toBe(compView); value = 'v2'; - expect(() => checkNoChangesView(view)) + expect(() => Services.checkNoChangesView(view)) .toThrowError( - `Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`); + `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`); }); it('should support detaching and attaching component views for dirty checking', () => { @@ -176,15 +160,15 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { const compView = asProviderData(view, 1).componentView; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); update.calls.reset(); compView.state &= ~ViewState.ChecksEnabled; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(update).not.toHaveBeenCalled(); compView.state |= ViewState.ChecksEnabled; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(update).toHaveBeenCalled(); }); @@ -212,40 +196,37 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { ], update, null, ViewFlags.OnPush)), ], - (view) => { - setCurrentNode(view, 1); - checkNodeInline(compInputValue); - })); + (check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); })); const compView = asProviderData(view, 1).componentView; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); // auto detach update.calls.reset(); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(update).not.toHaveBeenCalled(); // auto attach on input changes update.calls.reset(); compInputValue = 'v1'; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(update).toHaveBeenCalled(); // auto detach update.calls.reset(); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(update).not.toHaveBeenCalled(); // auto attach on events addListenerSpy.calls.mostRecent().args[1]('SomeEvent'); update.calls.reset(); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(update).toHaveBeenCalled(); // auto detach update.calls.reset(); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(update).not.toHaveBeenCalled(); }); } @@ -263,22 +244,19 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { NodeFlags.None, null, 0, AComp, [], null, null, () => compViewDef( [ - elementDef(NodeFlags.None, null, null, 0, 'span'), + elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), ], update)), ])); const compView = asProviderData(view, 1).componentView; - update.and.callFake((view: ViewData) => { - setCurrentNode(view, 0); - throw new Error('Test'); - }); - expect(() => checkAndUpdateView(view)).toThrow(); + update.and.callFake((check: NodeCheckFn, view: ViewData) => { throw new Error('Test'); }); + expect(() => Services.checkAndUpdateView(view)).toThrowError('Test'); expect(update).toHaveBeenCalled(); update.calls.reset(); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(update).not.toHaveBeenCalled(); }); @@ -304,7 +282,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { ])), ])); - destroyView(view); + Services.destroyView(view); expect(log).toEqual(['ngOnDestroy']); }); @@ -314,12 +292,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { [ elementDef(NodeFlags.None, null, null, 0, 'div'), ], - (view) => { setCurrentNode(view, 0); })); + (view) => {})); - destroyView(view); + Services.destroyView(view); - expect(() => checkAndUpdateView(view)) - .toThrowError('View has been used after destroy for CheckAndUpdate'); + expect(() => Services.checkAndUpdateView(view)) + .toThrowError('ViewDestroyedError: Attempt to use a destroyed view: detectChanges'); }); }); diff --git a/modules/@angular/core/test/view/element_spec.ts b/modules/@angular/core/test/view/element_spec.ts index 5a24887da2..13a37f91bc 100644 --- a/modules/@angular/core/test/view/element_spec.ts +++ b/modules/@angular/core/test/view/element_spec.ts @@ -8,40 +8,23 @@ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; import {getDebugContext} from '@angular/core/src/errors'; -import {BindingType, DebugContext, NodeDef, NodeFlags, RootData, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, destroyView, elementDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {ArgumentType, BindingType, DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, createRootData, isBrowser, removeNodes, setupAndCheckRenderer} from './helper'; +import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser, removeNodes} from './helper'; export function main() { - if (isBrowser()) { - defineTests({directDom: true, viewFlags: ViewFlags.DirectDom}); - } - defineTests({directDom: false, viewFlags: 0}); -} - -function defineTests(config: {directDom: boolean, viewFlags: number}) { - describe(`View Elements, directDom: ${config.directDom}`, () => { - setupAndCheckRenderer(config); - - let rootData: RootData; - let renderComponentType: RenderComponentType; - - beforeEach(() => { - rootData = createRootData(); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - }); - + describe(`View Elements`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, + viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, update, handleEvent); } function createAndGetRootNodes( viewDef: ViewDefinition, context?: any): {rootNodes: any[], view: ViewData} { - const view = createRootView(rootData, viewDef, context); + const view = createRootView(viewDef, context); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -81,19 +64,17 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(getDOM().getAttribute(rootNodes[0], 'title')).toBe('a'); }); - if (!config.directDom) { - it('should add debug information to the renderer', () => { - const someContext = new Object(); - const {view, rootNodes} = createAndGetRootNodes( - compViewDef([elementDef(NodeFlags.None, null, null, 0, 'div')]), someContext); - expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asElementData(view, 0).renderElement); - }); - } + it('should add debug information to the renderer', () => { + const someContext = new Object(); + const {view, rootNodes} = createAndGetRootNodes( + compViewDef([elementDef(NodeFlags.None, null, null, 0, 'div')]), someContext); + expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asElementData(view, 0).renderElement); + }); }); describe('change properties', () => { - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should update ${InlineDynamic[inlineDynamic]}`, () => { + ARG_TYPE_VALUES.forEach((inlineDynamic) => { + it(`should update ${ArgumentType[inlineDynamic]}`, () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef( [ @@ -104,27 +85,24 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { [BindingType.ElementProperty, 'value', SecurityContext.NONE] ]), ], - (view) => { - setCurrentNode(view, 0); - checkNodeInlineOrDynamic(inlineDynamic, ['v1', 'v2']); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']); })); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const el = rootNodes[0]; expect(getDOM().getProperty(el, 'title')).toBe('v1'); expect(getDOM().getProperty(el, 'value')).toBe('v2'); - if (!config.directDom) { - expect(getDOM().getAttribute(el, 'ng-reflect-title')).toBe('v1'); - } + expect(getDOM().getAttribute(el, 'ng-reflect-title')).toBe('v1'); }); }); }); describe('change attributes', () => { - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should update ${InlineDynamic[inlineDynamic]}`, () => { + ARG_TYPE_VALUES.forEach((inlineDynamic) => { + it(`should update ${ArgumentType[inlineDynamic]}`, () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef( [ elementDef( @@ -134,12 +112,11 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { [BindingType.ElementAttribute, 'a2', SecurityContext.NONE] ]), ], - (view) => { - setCurrentNode(view, 0); - checkNodeInlineOrDynamic(inlineDynamic, ['v1', 'v2']); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']); })); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const el = rootNodes[0]; expect(getDOM().getAttribute(el, 'a1')).toBe('v1'); @@ -149,20 +126,19 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { }); describe('change classes', () => { - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should update ${InlineDynamic[inlineDynamic]}`, () => { + ARG_TYPE_VALUES.forEach((inlineDynamic) => { + it(`should update ${ArgumentType[inlineDynamic]}`, () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef( [ elementDef( NodeFlags.None, null, null, 0, 'div', null, [[BindingType.ElementClass, 'c1'], [BindingType.ElementClass, 'c2']]), ], - (view) => { - setCurrentNode(view, 0); - checkNodeInlineOrDynamic(inlineDynamic, [true, true]); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [true, true]); })); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const el = rootNodes[0]; expect(getDOM().hasClass(el, 'c1')).toBeTruthy(); @@ -172,8 +148,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { }); describe('change styles', () => { - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should update ${InlineDynamic[inlineDynamic]}`, () => { + ARG_TYPE_VALUES.forEach((inlineDynamic) => { + it(`should update ${ArgumentType[inlineDynamic]}`, () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef( [ elementDef( @@ -183,12 +159,11 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { [BindingType.ElementStyle, 'color', null] ]), ], - (view) => { - setCurrentNode(view, 0); - checkNodeInlineOrDynamic(inlineDynamic, [10, 'red']); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [10, 'red']); })); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const el = rootNodes[0]; expect(getDOM().getStyle(el, 'width')).toBe('10px'); @@ -198,8 +173,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { }); describe('general binding behavior', () => { - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + ARG_TYPE_VALUES.forEach((inlineDynamic) => { + it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => { let bindingValue: any; const {view, rootNodes} = createAndGetRootNodes(compViewDef( @@ -210,25 +185,24 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { [BindingType.ElementProperty, 'someProp', SecurityContext.NONE], ]), ], - (view) => { - setCurrentNode(view, 0); - checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [bindingValue]); })); const setterSpy = jasmine.createSpy('set'); Object.defineProperty(rootNodes[0], 'someProp', {set: setterSpy}); bindingValue = 'v1'; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(setterSpy).toHaveBeenCalledWith('v1'); setterSpy.calls.reset(); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(setterSpy).not.toHaveBeenCalled(); setterSpy.calls.reset(); bindingValue = WrappedValue.wrap('v1'); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(setterSpy).toHaveBeenCalledWith('v1'); }); }); @@ -265,7 +239,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(handleEventArgs[2]).toBe('click'); expect(handleEventArgs[3]).toBeTruthy(); - destroyView(view); + Services.destroyView(view); expect(removeListenerSpy).toHaveBeenCalled(); }); @@ -291,7 +265,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(handleEventArgs[2]).toBe('windowClick'); expect(handleEventArgs[3]).toBeTruthy(); - destroyView(view); + Services.destroyView(view); expect(removeListenerSpy).toHaveBeenCalled(); }); @@ -317,7 +291,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(handleEventArgs[2]).toBe('documentClick'); expect(handleEventArgs[3]).toBeTruthy(); - destroyView(view); + Services.destroyView(view); expect(removeListenerSpy).toHaveBeenCalled(); }); diff --git a/modules/@angular/core/test/view/embedded_view_spec.ts b/modules/@angular/core/test/view/embedded_view_spec.ts index 2f71cbe071..e325bc93eb 100644 --- a/modules/@angular/core/test/view/embedded_view_spec.ts +++ b/modules/@angular/core/test/view/embedded_view_spec.ts @@ -7,44 +7,27 @@ */ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {BindingType, NodeDef, NodeFlags, RootData, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, attachEmbeddedView, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createEmbeddedView, createRootView, destroyView, detachEmbeddedView, directiveDef, elementDef, moveEmbeddedView, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {ArgumentType, BindingType, NodeCheckFn, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, moveEmbeddedView, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {createRootData, isBrowser, setupAndCheckRenderer} from './helper'; +import {createRootView, isBrowser} from './helper'; export function main() { - if (isBrowser()) { - defineTests({directDom: true, viewFlags: ViewFlags.DirectDom}); - } - defineTests({directDom: false, viewFlags: 0}); -} - -function defineTests(config: {directDom: boolean, viewFlags: number}) { - describe(`Embedded Views, directDom: ${config.directDom}`, () => { - setupAndCheckRenderer(config); - - let rootData: RootData; - let renderComponentType: RenderComponentType; - - beforeEach(() => { - rootData = createRootData(); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - }); - + describe(`Embedded Views`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, + viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, update, handleEvent); } function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, update); + return viewDef(ViewFlags.None, nodes, update); } function createAndGetRootNodes( viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} { - const view = createRootView(rootData, viewDef, context); + const view = createRootView(viewDef, context); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -62,7 +45,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { ]), parentContext); - const childView = createEmbeddedView(parentView, parentView.def.nodes[1], childContext); + const childView = + Services.createEmbeddedView(parentView, parentView.def.nodes[1], childContext); expect(childView.component).toBe(parentContext); expect(childView.context).toBe(childContext); }); @@ -79,8 +63,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { ])); const viewContainerData = asElementData(parentView, 1); - const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]); - const childView1 = createEmbeddedView(parentView, parentView.def.nodes[2]); + const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]); + const childView1 = Services.createEmbeddedView(parentView, parentView.def.nodes[2]); attachEmbeddedView(viewContainerData, 0, childView0); attachEmbeddedView(viewContainerData, 1, childView1); @@ -109,8 +93,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { ])); const viewContainerData = asElementData(parentView, 1); - const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]); - const childView1 = createEmbeddedView(parentView, parentView.def.nodes[2]); + const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]); + const childView1 = Services.createEmbeddedView(parentView, parentView.def.nodes[2]); attachEmbeddedView(viewContainerData, 0, childView0); attachEmbeddedView(viewContainerData, 1, childView1); @@ -133,7 +117,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'after'}) ])); - const childView0 = createEmbeddedView(parentView, parentView.def.nodes[0]); + const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[0]); attachEmbeddedView(asElementData(parentView, 0), 0, childView0); const rootNodes = rootRenderNodes(parentView); @@ -144,10 +128,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { it('should dirty check embedded views', () => { let childValue = 'v1'; - const update = jasmine.createSpy('updater').and.callFake((view: ViewData) => { - setCurrentNode(view, 0); - checkNodeInline(childValue); - }); + const update = + jasmine.createSpy('updater').and.callFake((check: NodeCheckFn, view: ViewData) => { + check(view, 0, ArgumentType.Inline, childValue); + }); const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 1, 'div'), @@ -160,24 +144,24 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { update)) ])); - const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]); + const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]); const rootEl = rootNodes[0]; attachEmbeddedView(asElementData(parentView, 1), 0, childView0); - checkAndUpdateView(parentView); + Services.checkAndUpdateView(parentView); - expect(update).toHaveBeenCalledWith(childView0); + expect(update.calls.mostRecent().args[1]).toBe(childView0); update.calls.reset(); - checkNoChangesView(parentView); + Services.checkNoChangesView(parentView); - expect(update).toHaveBeenCalledWith(childView0); + expect(update.calls.mostRecent().args[1]).toBe(childView0); childValue = 'v2'; - expect(() => checkNoChangesView(parentView)) + expect(() => Services.checkNoChangesView(parentView)) .toThrowError( - `Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`); + `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`); }); it('should destroy embedded views', () => { @@ -195,10 +179,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { ])) ])); - const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]); + const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]); attachEmbeddedView(asElementData(parentView, 1), 0, childView0); - destroyView(parentView); + Services.destroyView(parentView); expect(log).toEqual(['ngOnDestroy']); }); diff --git a/modules/@angular/core/test/view/helper.ts b/modules/@angular/core/test/view/helper.ts index 7ae6c2008e..bb4906ba6b 100644 --- a/modules/@angular/core/test/view/helper.ts +++ b/modules/@angular/core/test/view/helper.ts @@ -7,7 +7,7 @@ */ import {Injector, RootRenderer, Sanitizer} from '@angular/core'; -import {RootData, checkNodeDynamic, checkNodeInline} from '@angular/core/src/view/index'; +import {ArgumentType, NodeCheckFn, RootData, Services, ViewData, ViewDefinition, initServicesIfNeeded} from '@angular/core/src/view/index'; import {TestBed} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -15,53 +15,25 @@ export function isBrowser() { return getDOM().supportsDOMEvents(); } -export function setupAndCheckRenderer(config: {directDom: boolean}) { - let rootRenderer: any; - if (config.directDom) { - beforeEach(() => { - rootRenderer = { - renderComponent: jasmine.createSpy('renderComponent') - .and.throwError('Renderer should not have been called!') - }; - TestBed.configureTestingModule( - {providers: [{provide: RootRenderer, useValue: rootRenderer}]}); - }); - afterEach(() => { expect(rootRenderer.renderComponent).not.toHaveBeenCalled(); }); - } else { - beforeEach(() => { - rootRenderer = TestBed.get(RootRenderer); - spyOn(rootRenderer, 'renderComponent').and.callThrough(); - }); - afterEach(() => { expect(rootRenderer.renderComponent).toHaveBeenCalled(); }); +export const ARG_TYPE_VALUES = [ArgumentType.Inline, ArgumentType.Dynamic]; + +export function checkNodeInlineOrDynamic( + check: NodeCheckFn, view: ViewData, nodeIndex: number, argType: ArgumentType, + values: any[]): any { + switch (argType) { + case ArgumentType.Inline: + return (check)(view, nodeIndex, argType, ...values); + case ArgumentType.Dynamic: + return check(view, nodeIndex, argType, values); } } -export enum InlineDynamic { - Inline, - Dynamic -} - -export const INLINE_DYNAMIC_VALUES = [InlineDynamic.Inline, InlineDynamic.Dynamic]; - -export function checkNodeInlineOrDynamic(inlineDynamic: InlineDynamic, values: any[]): any { - switch (inlineDynamic) { - case InlineDynamic.Inline: - return (checkNodeInline)(...values); - case InlineDynamic.Dynamic: - return checkNodeDynamic(values); - } -} - -export function createRootData(projectableNodes?: any[][], rootSelectorOrNode?: any): RootData { - const injector = TestBed.get(Injector); - const renderer = injector.get(RootRenderer); - const sanitizer = injector.get(Sanitizer); - projectableNodes = projectableNodes || []; - return { - injector, - projectableNodes, - selectorOrNode: rootSelectorOrNode, sanitizer, renderer - }; +export function createRootView( + def: ViewDefinition, context?: any, projectableNodes?: any[][], + rootSelectorOrNode?: any): ViewData { + initServicesIfNeeded(); + return Services.createRootView( + TestBed.get(Injector), projectableNodes || [], rootSelectorOrNode, def, context); } export let removeNodes: Node[]; diff --git a/modules/@angular/core/test/view/ng_content_spec.ts b/modules/@angular/core/test/view/ng_content_spec.ts index ba8cb453ba..a83ec98d62 100644 --- a/modules/@angular/core/test/view/ng_content_spec.ts +++ b/modules/@angular/core/test/view/ng_content_spec.ts @@ -7,39 +7,22 @@ */ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core'; -import {DebugContext, NodeDef, NodeFlags, RootData, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, asTextData, attachEmbeddedView, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createEmbeddedView, createRootView, detachEmbeddedView, directiveDef, elementDef, ngContentDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, asTextData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, ngContentDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {createRootData, isBrowser, setupAndCheckRenderer} from './helper'; +import {createRootView, isBrowser} from './helper'; export function main() { - if (isBrowser()) { - defineTests({directDom: true, viewFlags: ViewFlags.DirectDom}); - } - defineTests({directDom: false, viewFlags: 0}); -} - -function defineTests(config: {directDom: boolean, viewFlags: number}) { - describe(`View NgContent, directDom: ${config.directDom}`, () => { - setupAndCheckRenderer(config); - - let rootData: RootData; - let renderComponentType: RenderComponentType; - - beforeEach(() => { - rootData = createRootData(); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - }); - + describe(`View NgContent`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, + viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, update, handleEvent); } function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, update); + return viewDef(ViewFlags.None, nodes, update); } function hostElDef(contentNodes: NodeDef[], viewNodes: NodeDef[]): NodeDef[] { @@ -56,7 +39,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { function createAndGetRootNodes( viewDef: ViewDefinition, ctx?: any): {rootNodes: any[], view: ViewData} { - const view = createRootView(rootData, viewDef, ctx || {}); + const view = createRootView(viewDef, ctx || {}); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -125,7 +108,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { ]))); const componentView = asProviderData(view, 1).componentView; - const view0 = createEmbeddedView(componentView, componentView.def.nodes[1]); + const view0 = Services.createEmbeddedView(componentView, componentView.def.nodes[1]); attachEmbeddedView(asElementData(componentView, 1), 0, view0); expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0])).length).toBe(2); @@ -138,14 +121,14 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { if (isBrowser()) { it('should use root projectable nodes', () => { - rootData.projectableNodes = - [[document.createTextNode('a')], [document.createTextNode('b')]]; + const projectableNodes = [[document.createTextNode('a')], [document.createTextNode('b')]]; + const view = createRootView( + compViewDef(hostElDef([], [ngContentDef(null, 0), ngContentDef(null, 1)])), {}, + projectableNodes); + const rootNodes = rootRenderNodes(view); - const {view, rootNodes} = createAndGetRootNodes( - compViewDef(hostElDef([], [ngContentDef(null, 0), ngContentDef(null, 1)]))); - - expect(getDOM().childNodes(rootNodes[0])[0]).toBe(rootData.projectableNodes[0][0]); - expect(getDOM().childNodes(rootNodes[0])[1]).toBe(rootData.projectableNodes[1][0]); + expect(getDOM().childNodes(rootNodes[0])[0]).toBe(projectableNodes[0][0]); + expect(getDOM().childNodes(rootNodes[0])[1]).toBe(projectableNodes[1][0]); }); } }); diff --git a/modules/@angular/core/test/view/provider_spec.ts b/modules/@angular/core/test/view/provider_spec.ts index 5431f39a61..8ff988c7c8 100644 --- a/modules/@angular/core/test/view/provider_spec.ts +++ b/modules/@angular/core/test/view/provider_spec.ts @@ -8,43 +8,27 @@ import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectorRef, DoCheck, ElementRef, EventEmitter, Injector, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; import {getDebugContext} from '@angular/core/src/errors'; -import {BindingType, DebugContext, DepFlags, NodeDef, NodeFlags, ProviderType, RootData, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, destroyView, directiveDef, elementDef, providerDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; -import {inject} from '@angular/core/testing'; +import {ArgumentType, BindingType, DebugContext, DepFlags, NodeDef, NodeFlags, ProviderType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, directiveDef, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {TestBed, inject, withModule} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, createRootData, isBrowser, setupAndCheckRenderer} from './helper'; +import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser} from './helper'; export function main() { - if (isBrowser()) { - defineTests({directDom: true, viewFlags: ViewFlags.DirectDom}); - } - defineTests({directDom: false, viewFlags: 0}); -} - -function defineTests(config: {directDom: boolean, viewFlags: number}) { - describe(`View Providers, directDom: ${config.directDom}`, () => { - setupAndCheckRenderer(config); - - let rootData: RootData; - let renderComponentType: RenderComponentType; - - beforeEach(() => { - rootData = createRootData(); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - }); - + describe(`View Providers`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, + viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef( + viewFlags, nodes, update, handleEvent, 'someCompId', ViewEncapsulation.None, []); } function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, update); + return viewDef(ViewFlags.None, nodes, update); } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { - const view = createRootView(rootData, viewDef); + const view = createRootView(viewDef, {}); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -127,10 +111,14 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { let err: any; try { - createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'span'), - directiveDef(NodeFlags.None, null, 0, SomeService, []) - ])); + createRootView( + compViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div'), + directiveDef( + NodeFlags.None, null, 0, SomeService, [], null, null, + () => compViewDef([textDef(null, ['a'])])) + ]), + TestBed.get(Injector), [], getDOM().createElement('div')); } catch (e) { err = e; } @@ -138,8 +126,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(err.message).toBe('Test'); const debugCtx = getDebugContext(err); expect(debugCtx.view).toBeTruthy(); - // errors should point to the already existing element - expect(debugCtx.nodeIndex).toBe(0); + expect(debugCtx.nodeIndex).toBe(1); }); describe('deps', () => { @@ -229,17 +216,15 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(instance.dep).toBe('someParentValue'); }); - it('should ask the root injector', () => { - const getSpy = spyOn(rootData.injector, 'get'); - getSpy.and.returnValue('rootValue'); - createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'span'), - directiveDef(NodeFlags.None, null, 0, SomeService, ['rootDep']) - ])); + it('should ask the root injector', + withModule({providers: [{provide: 'rootDep', useValue: 'rootValue'}]}, () => { + createAndGetRootNodes(compViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'span'), + directiveDef(NodeFlags.None, null, 0, SomeService, ['rootDep']) + ])); - expect(instance.dep).toBe('rootValue'); - expect(getSpy).toHaveBeenCalledWith('rootDep', Injector.THROW_IF_NOT_FOUND); - }); + expect(instance.dep).toBe('rootValue'); + })); describe('builtin tokens', () => { it('should inject ViewContainerRef', () => { @@ -302,24 +287,14 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(instance.dep._view).toBe(compView); }); - if (config.directDom) { - it('should not inject Renderer when using directDom', () => { - expect(() => createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'span'), - directiveDef(NodeFlags.None, null, 0, SomeService, [Renderer]) - ]))) - .toThrowError('No provider for Renderer!'); - }); - } else { - it('should inject Renderer when not using directDom', () => { - createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'span'), - directiveDef(NodeFlags.None, null, 0, SomeService, [Renderer]) - ])); + it('should inject RendererV1', () => { + createAndGetRootNodes(compViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'span'), + directiveDef(NodeFlags.None, null, 0, SomeService, [Renderer]) + ])); - expect(instance.dep.createElement).toBeTruthy(); - }); - } + expect(instance.dep.createElement).toBeTruthy(); + }); }); }); @@ -327,8 +302,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe('data binding', () => { - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should update ${InlineDynamic[inlineDynamic]}`, () => { + ARG_TYPE_VALUES.forEach((inlineDynamic) => { + it(`should update ${ArgumentType[inlineDynamic]}`, () => { let instance: SomeService; class SomeService { @@ -342,23 +317,20 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { elementDef(NodeFlags.None, null, null, 1, 'span'), directiveDef(NodeFlags.None, null, 0, SomeService, [], {a: [0, 'a'], b: [1, 'b']}) ], - (view) => { - setCurrentNode(view, 1); - checkNodeInlineOrDynamic(inlineDynamic, ['v1', 'v2']); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, ['v1', 'v2']); })); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(instance.a).toBe('v1'); expect(instance.b).toBe('v2'); - if (!config.directDom) { - const el = rootNodes[0]; - expect(getDOM().getAttribute(el, 'ng-reflect-a')).toBe('v1'); - } + const el = rootNodes[0]; + expect(getDOM().getAttribute(el, 'ng-reflect-a')).toBe('v1'); }); - it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => { let bindingValue: any; let setterSpy = jasmine.createSpy('set'); @@ -371,22 +343,21 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { elementDef(NodeFlags.None, null, null, 1, 'span'), directiveDef(NodeFlags.None, null, 0, SomeService, [], {a: [0, 'a']}) ], - (view) => { - setCurrentNode(view, 1); - checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, [bindingValue]); })); bindingValue = 'v1'; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(setterSpy).toHaveBeenCalledWith('v1'); setterSpy.calls.reset(); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(setterSpy).not.toHaveBeenCalled(); setterSpy.calls.reset(); bindingValue = WrappedValue.wrap('v1'); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(setterSpy).toHaveBeenCalledWith('v1'); }); @@ -422,7 +393,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { emitter.emit('someEventInstance'); expect(handleEvent).toHaveBeenCalledWith(view, 0, 'someEventName', 'someEventInstance'); - destroyView(view); + Services.destroyView(view); expect(unsubscribeSpy).toHaveBeenCalled(); }); @@ -485,14 +456,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { elementDef(NodeFlags.None, null, null, 1, 'span'), directiveDef(allFlags, null, 0, SomeService, [], {a: [0, 'a']}) ], - (updater) => { - setCurrentNode(view, 1); - checkNodeInline('someValue'); - setCurrentNode(view, 3); - checkNodeInline('someValue'); + (check, view) => { + check(view, 1, ArgumentType.Inline, 'someValue'); + check(view, 3, ArgumentType.Inline, 'someValue'); })); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); // Note: After... hooks are called bottom up. expect(log).toEqual([ @@ -513,7 +482,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { ]); log = []; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); // Note: After... hooks are called bottom up. expect(log).toEqual([ @@ -522,7 +491,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { ]); log = []; - destroyView(view); + Services.destroyView(view); // Note: ngOnDestroy ist called bottom up. expect(log).toEqual(['1_ngOnDestroy', '0_ngOnDestroy']); @@ -545,17 +514,14 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { directiveDef( NodeFlags.OnChanges, null, 0, SomeService, [], {a: [0, 'nonMinifiedA']}) ], - (updater) => { - setCurrentNode(view, 1); - checkNodeInline(currValue); - })); + (check, view) => { check(view, 1, ArgumentType.Inline, currValue); })); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(changesLog).toEqual([new SimpleChange(undefined, 'v1', true)]); currValue = 'v2'; changesLog = []; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(changesLog).toEqual([new SimpleChange('v1', 'v2', false)]); }); @@ -571,7 +537,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { let err: any; try { - checkAndUpdateView(view); + Services.checkAndUpdateView(view); } catch (e) { err = e; } @@ -582,7 +548,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(debugCtx.nodeIndex).toBe(1); }); - it('should add a DebugContext to errors in destroyView', () => { + it('should add a DebugContext to errors inServices.destroyView', () => { class SomeService implements OnDestroy { ngOnDestroy() { throw new Error('Test'); } } @@ -594,7 +560,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { let err: any; try { - destroyView(view); + Services.destroyView(view); } catch (e) { err = e; } diff --git a/modules/@angular/core/test/view/pure_expression_spec.ts b/modules/@angular/core/test/view/pure_expression_spec.ts index dacd73a8a9..b0078d285a 100644 --- a/modules/@angular/core/test/view/pure_expression_spec.ts +++ b/modules/@angular/core/test/view/pure_expression_spec.ts @@ -7,29 +7,21 @@ */ import {Injector, PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue} from '@angular/core'; -import {NodeDef, NodeFlags, RootData, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, asPureExpressionData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, directiveDef, elementDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {ArgumentType, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, asPureExpressionData, directiveDef, elementDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; -import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, createRootData} from './helper'; +import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView} from './helper'; export function main() { describe(`View Pure Expressions`, () => { - let rootData: RootData; - let renderComponentType: RenderComponentType; - - beforeEach(() => { - rootData = createRootData(); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - }); - function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { - return viewDef(ViewFlags.None, nodes, update, handleEvent, renderComponentType); + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, + viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, update, handleEvent); } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { - const view = createRootView(rootData, viewDef); + const view = createRootView(viewDef); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -40,8 +32,8 @@ export function main() { describe('pure arrays', () => { - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should support ${InlineDynamic[inlineDynamic]} bindings`, () => { + ARG_TYPE_VALUES.forEach((inlineDynamic) => { + it(`should support ${ArgumentType[inlineDynamic]} bindings`, () => { let values: any[]; const {view, rootNodes} = createAndGetRootNodes(compViewDef( @@ -49,55 +41,52 @@ export function main() { elementDef(NodeFlags.None, null, null, 2, 'span'), pureArrayDef(2), directiveDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']}) ], - (view) => { - setCurrentNode(view, 1); - const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values); - setCurrentNode(view, 2); - checkNodeInlineOrDynamic(inlineDynamic, [pureValue]); + (check, view) => { + const pureValue = checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, values); + checkNodeInlineOrDynamic(check, view, 2, inlineDynamic, [pureValue]); })); const service = asProviderData(view, 2).instance; values = [1, 2]; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const arr0 = service.data; expect(arr0).toEqual([1, 2]); // instance should not change // if the values don't change - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(service.data).toBe(arr0); values = [3, 2]; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const arr1 = service.data; expect(arr1).not.toBe(arr0); expect(arr1).toEqual([3, 2]); }); - it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => { let bindingValue: any; const {view, rootNodes} = createAndGetRootNodes(compViewDef( [ elementDef(NodeFlags.None, null, null, 1, 'span'), pureArrayDef(1), ], - (view) => { - setCurrentNode(view, 1); - checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, [bindingValue]); })); const exprData = asPureExpressionData(view, 1); bindingValue = 'v1'; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const v1Arr = exprData.value; expect(v1Arr).toEqual(['v1']); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(exprData.value).toBe(v1Arr); bindingValue = WrappedValue.wrap('v1'); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(exprData.value).not.toBe(v1Arr); expect(exprData.value).toEqual(['v1']); }); @@ -106,8 +95,8 @@ export function main() { }); describe('pure objects', () => { - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should support ${InlineDynamic[inlineDynamic]} bindings`, () => { + ARG_TYPE_VALUES.forEach((inlineDynamic) => { + it(`should support ${ArgumentType[inlineDynamic]} bindings`, () => { let values: any[]; const {view, rootNodes} = createAndGetRootNodes(compViewDef( @@ -115,55 +104,52 @@ export function main() { elementDef(NodeFlags.None, null, null, 2, 'span'), pureObjectDef(['a', 'b']), directiveDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']}) ], - (view) => { - setCurrentNode(view, 1); - const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values); - setCurrentNode(view, 2); - checkNodeInlineOrDynamic(inlineDynamic, [pureValue]); + (check, view) => { + const pureValue = checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, values); + checkNodeInlineOrDynamic(check, view, 2, inlineDynamic, [pureValue]); })); const service = asProviderData(view, 2).instance; values = [1, 2]; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const obj0 = service.data; expect(obj0).toEqual({a: 1, b: 2}); // instance should not change // if the values don't change - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(service.data).toBe(obj0); values = [3, 2]; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const obj1 = service.data; expect(obj1).not.toBe(obj0); expect(obj1).toEqual({a: 3, b: 2}); }); - it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => { let bindingValue: any; const {view, rootNodes} = createAndGetRootNodes(compViewDef( [ elementDef(NodeFlags.None, null, null, 1, 'span'), pureObjectDef(['a']), ], - (view) => { - setCurrentNode(view, 1); - checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, [bindingValue]); })); const exprData = asPureExpressionData(view, 1); bindingValue = 'v1'; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const v1Obj = exprData.value; expect(v1Obj).toEqual({'a': 'v1'}); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(exprData.value).toBe(v1Obj); bindingValue = WrappedValue.wrap('v1'); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(exprData.value).not.toBe(v1Obj); expect(exprData.value).toEqual({'a': 'v1'}); }); @@ -171,8 +157,8 @@ export function main() { }); describe('pure pipes', () => { - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should support ${InlineDynamic[inlineDynamic]} bindings`, () => { + ARG_TYPE_VALUES.forEach((inlineDynamic) => { + it(`should support ${ArgumentType[inlineDynamic]} bindings`, () => { class SomePipe implements PipeTransform { transform(v1: any, v2: any) { return [v1 + 10, v2 + 20]; } } @@ -185,32 +171,30 @@ export function main() { directiveDef(NodeFlags.None, null, 0, SomePipe, []), purePipeDef(SomePipe, 2), directiveDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']}) ], - (view) => { - setCurrentNode(view, 2); - const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values); - setCurrentNode(view, 3); - checkNodeInlineOrDynamic(inlineDynamic, [pureValue]); + (check, view) => { + const pureValue = checkNodeInlineOrDynamic(check, view, 2, inlineDynamic, values); + checkNodeInlineOrDynamic(check, view, 3, inlineDynamic, [pureValue]); })); const service = asProviderData(view, 3).instance; values = [1, 2]; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const obj0 = service.data; expect(obj0).toEqual([11, 22]); // instance should not change // if the values don't change - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(service.data).toBe(obj0); values = [3, 2]; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const obj1 = service.data; expect(obj1).not.toBe(obj0); expect(obj1).toEqual([13, 22]); }); - it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => { let bindingValue: any; let transformSpy = jasmine.createSpy('transform'); @@ -224,21 +208,20 @@ export function main() { directiveDef(NodeFlags.None, null, 0, SomePipe, []), purePipeDef(SomePipe, 1), ], - (view) => { - setCurrentNode(view, 2); - checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 2, inlineDynamic, [bindingValue]); })); bindingValue = 'v1'; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(transformSpy).toHaveBeenCalledWith('v1'); transformSpy.calls.reset(); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(transformSpy).not.toHaveBeenCalled(); bindingValue = WrappedValue.wrap('v1'); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(transformSpy).toHaveBeenCalledWith('v1'); }); }); diff --git a/modules/@angular/core/test/view/query_spec.ts b/modules/@angular/core/test/view/query_spec.ts index 4b8e0e95a5..9d098a5625 100644 --- a/modules/@angular/core/test/view/query_spec.ts +++ b/modules/@angular/core/test/view/query_spec.ts @@ -8,26 +8,18 @@ import {ElementRef, Injector, QueryList, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core'; import {getDebugContext} from '@angular/core/src/errors'; -import {BindingType, DebugContext, NodeDef, NodeFlags, QueryBindingType, QueryValueType, RootData, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, attachEmbeddedView, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createEmbeddedView, createRootView, destroyView, detachEmbeddedView, directiveDef, elementDef, queryDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {BindingType, DebugContext, NodeDef, NodeFlags, QueryBindingType, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, queryDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {createRootData} from './helper'; +import {createRootView} from './helper'; export function main() { describe(`Query Views`, () => { - let rootData: RootData; - let renderComponentType: RenderComponentType; - - beforeEach(() => { - rootData = createRootData(); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - }); - function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { - return viewDef(ViewFlags.None, nodes, update, handleEvent, renderComponentType); + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, + viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, update, handleEvent); } function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinition { @@ -36,7 +28,7 @@ export function main() { function createAndGetRootNodes( viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} { - const view = createRootView(rootData, viewDef, context); + const view = createRootView(viewDef, context); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -79,7 +71,7 @@ export function main() { const qs: QueryService = asProviderData(view, 1).instance; expect(qs.a).toBeUndefined(); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const as = qs.a.toArray(); expect(as.length).toBe(2); @@ -97,7 +89,7 @@ export function main() { aServiceProvider(), ])); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const qs: QueryService = asProviderData(view, 3).instance; expect(qs.a.length).toBe(0); @@ -114,7 +106,7 @@ export function main() { ])), ])); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const comp: QueryService = asProviderData(view, 1).instance; const compView = asProviderData(view, 1).componentView; @@ -131,7 +123,7 @@ export function main() { aServiceProvider(), ])); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const comp: QueryService = asProviderData(view, 1).instance; expect(comp.a.length).toBe(0); }); @@ -153,9 +145,9 @@ export function main() { ...contentQueryProviders(), ])); - const childView = createEmbeddedView(view, view.def.nodes[3]); + const childView = Services.createEmbeddedView(view, view.def.nodes[3]); attachEmbeddedView(asElementData(view, 3), 0, childView); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); // queries on parent elements of anchors const qs1: QueryService = asProviderData(view, 1).instance; @@ -185,11 +177,11 @@ export function main() { anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0), ])); - const childView = createEmbeddedView(view, view.def.nodes[3]); + const childView = Services.createEmbeddedView(view, view.def.nodes[3]); // attach at a different place than the one where the template was defined attachEmbeddedView(asElementData(view, 7), 0, childView); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); // query on the declaration place const qs1: QueryService = asProviderData(view, 1).instance; @@ -215,19 +207,19 @@ export function main() { ])), ])); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const qs: QueryService = asProviderData(view, 1).instance; expect(qs.a.length).toBe(0); - const childView = createEmbeddedView(view, view.def.nodes[3]); + const childView = Services.createEmbeddedView(view, view.def.nodes[3]); attachEmbeddedView(asElementData(view, 3), 0, childView); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(qs.a.length).toBe(1); detachEmbeddedView(asElementData(view, 3), 0); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(qs.a.length).toBe(0); }); @@ -247,20 +239,20 @@ export function main() { ])), ])); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const comp: QueryService = asProviderData(view, 1).instance; expect(comp.a.length).toBe(0); const compView = asProviderData(view, 1).componentView; - const childView = createEmbeddedView(compView, compView.def.nodes[0]); + const childView = Services.createEmbeddedView(compView, compView.def.nodes[0]); attachEmbeddedView(asElementData(compView, 0), 0, childView); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(comp.a.length).toBe(1); detachEmbeddedView(asElementData(compView, 0), 0); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(comp.a.length).toBe(0); }); @@ -280,7 +272,7 @@ export function main() { aServiceProvider(), ])); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const qs: QueryService = asProviderData(view, 1).instance; expect(qs.a instanceof QueryList).toBeTruthy(); @@ -303,7 +295,7 @@ export function main() { aServiceProvider(), ])); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const qs: QueryService = asProviderData(view, 1).instance; expect(qs.a).toBe(asProviderData(view, 3).instance); @@ -322,7 +314,7 @@ export function main() { queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}), ])); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const qs: QueryService = asProviderData(view, 1).instance; expect(qs.a.nativeElement).toBe(asElementData(view, 0).renderElement); @@ -341,7 +333,7 @@ export function main() { queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}), ])); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const qs: QueryService = asProviderData(view, 1).instance; expect(qs.a.createEmbeddedView).toBeTruthy(); @@ -358,7 +350,7 @@ export function main() { queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}), ])); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const qs: QueryService = asProviderData(view, 1).instance; expect(qs.a.createEmbeddedView).toBeTruthy(); @@ -380,22 +372,22 @@ export function main() { ])), ])); - checkAndUpdateView(view); - checkNoChangesView(view); + Services.checkAndUpdateView(view); + Services.checkNoChangesView(view); - const childView = createEmbeddedView(view, view.def.nodes[3]); + const childView = Services.createEmbeddedView(view, view.def.nodes[3]); attachEmbeddedView(asElementData(view, 3), 0, childView); let err: any; try { - checkNoChangesView(view); + Services.checkNoChangesView(view); } catch (e) { err = e; } expect(err).toBeTruthy(); expect(err.message) .toBe( - `Expression has changed after it was checked. Previous value: 'Query query1 not dirty'. Current value: 'Query query1 dirty'.`); + `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'Query query1 not dirty'. Current value: 'Query query1 dirty'.`); const debugCtx = getDebugContext(err); expect(debugCtx.view).toBe(view); expect(debugCtx.nodeIndex).toBe(2); @@ -416,7 +408,7 @@ export function main() { let err: any; try { - checkAndUpdateView(view); + Services.checkAndUpdateView(view); } catch (e) { err = e; } diff --git a/modules/@angular/core/test/view/refs_spec.ts b/modules/@angular/core/test/view/services_spec.ts similarity index 72% rename from modules/@angular/core/test/view/refs_spec.ts rename to modules/@angular/core/test/view/services_spec.ts index 7c9abceb05..b0cb010462 100644 --- a/modules/@angular/core/test/view/refs_spec.ts +++ b/modules/@angular/core/test/view/services_spec.ts @@ -7,31 +7,23 @@ */ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, getDebugNode} from '@angular/core'; -import {DebugContext, NodeDef, NodeFlags, QueryValueType, Refs, RootData, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, asTextData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, directiveDef, elementDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {DebugContext, NodeDef, NodeFlags, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, asTextData, directiveDef, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {createRootData, isBrowser, setupAndCheckRenderer} from './helper'; +import {createRootView, isBrowser} from './helper'; export function main() { - describe('View References', () => { - let rootData: RootData; - let renderComponentType: RenderComponentType; - - beforeEach(() => { - rootData = createRootData(); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - }); - + describe('View Services', () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { - return viewDef(ViewFlags.None, nodes, update, handleEvent, renderComponentType); + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, + viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, update, handleEvent); } function createAndGetRootNodes( viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} { - const view = createRootView(rootData, viewDef, context); + const view = createRootView(viewDef, context); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -58,7 +50,7 @@ export function main() { const view = createViewWithData(); const compView = asProviderData(view, 1).componentView; - const debugCtx = Refs.createDebugContext(compView, 0); + const debugCtx = Services.createDebugContext(compView, 0); expect(debugCtx.componentRenderElement).toBe(asElementData(view, 0).renderElement); expect(debugCtx.renderNode).toBe(asElementData(compView, 0).renderElement); @@ -75,7 +67,7 @@ export function main() { const view = createViewWithData(); const compView = asProviderData(view, 1).componentView; - const debugCtx = Refs.createDebugContext(compView, 2); + const debugCtx = Services.createDebugContext(compView, 2); expect(debugCtx.componentRenderElement).toBe(asElementData(view, 0).renderElement); expect(debugCtx.renderNode).toBe(asTextData(compView, 2).renderText); @@ -89,7 +81,7 @@ export function main() { const view = createViewWithData(); const compView = asProviderData(view, 1).componentView; - const debugCtx = Refs.createDebugContext(compView, 1); + const debugCtx = Services.createDebugContext(compView, 1); expect(debugCtx.renderNode).toBe(asElementData(compView, 0).renderElement); }); diff --git a/modules/@angular/core/test/view/text_spec.ts b/modules/@angular/core/test/view/text_spec.ts index 23a18f7475..f68a7ad875 100644 --- a/modules/@angular/core/test/view/text_spec.ts +++ b/modules/@angular/core/test/view/text_spec.ts @@ -7,40 +7,23 @@ */ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; -import {DebugContext, NodeDef, NodeFlags, RootData, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asTextData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, elementDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {ArgumentType, DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asTextData, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, createRootData, isBrowser, setupAndCheckRenderer} from './helper'; +import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser} from './helper'; export function main() { - if (isBrowser()) { - defineTests({directDom: true, viewFlags: ViewFlags.DirectDom}); - } - defineTests({directDom: false, viewFlags: 0}); -} - -function defineTests(config: {directDom: boolean, viewFlags: number}) { - describe(`View Text, directDom: ${config.directDom}`, () => { - setupAndCheckRenderer(config); - - let rootData: RootData; - let renderComponentType: RenderComponentType; - - beforeEach(() => { - rootData = createRootData(); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - }); - + describe(`View Text`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, + viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, update, handleEvent); } function createAndGetRootNodes( viewDef: ViewDefinition, context?: any): {rootNodes: any[], view: ViewData} { - const view = createRootView(rootData, viewDef, context); + const view = createRootView(viewDef, context); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -69,36 +52,33 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(getDOM().getText(textNode)).toBe('a'); }); - if (!config.directDom) { - it('should add debug information to the renderer', () => { - const someContext = new Object(); - const {view, rootNodes} = - createAndGetRootNodes(compViewDef([textDef(null, ['a'])]), someContext); - expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asTextData(view, 0).renderText); - }); - } + it('should add debug information to the renderer', () => { + const someContext = new Object(); + const {view, rootNodes} = + createAndGetRootNodes(compViewDef([textDef(null, ['a'])]), someContext); + expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asTextData(view, 0).renderText); + }); }); describe('change text', () => { - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should update ${InlineDynamic[inlineDynamic]}`, () => { + ARG_TYPE_VALUES.forEach((inlineDynamic) => { + it(`should update ${ArgumentType[inlineDynamic]}`, () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef( [ textDef(null, ['0', '1', '2']), ], - (view: ViewData) => { - setCurrentNode(view, 0); - checkNodeInlineOrDynamic(inlineDynamic, ['a', 'b']); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['a', 'b']); })); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); const node = rootNodes[0]; expect(getDOM().getText(rootNodes[0])).toBe('0a1b2'); }); if (isBrowser()) { - it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => { let bindingValue: any; const setterSpy = jasmine.createSpy('set'); @@ -112,24 +92,23 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { [ textDef(null, ['', '']), ], - (view: ViewData) => { - setCurrentNode(view, 0); - checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + (check, view) => { + checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [bindingValue]); })); Object.defineProperty(rootNodes[0], 'nodeValue', {set: setterSpy}); bindingValue = 'v1'; - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(setterSpy).toHaveBeenCalledWith('v1'); setterSpy.calls.reset(); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(setterSpy).not.toHaveBeenCalled(); setterSpy.calls.reset(); bindingValue = WrappedValue.wrap('v1'); - checkAndUpdateView(view); + Services.checkAndUpdateView(view); expect(setterSpy).toHaveBeenCalledWith('v1'); }); } diff --git a/modules/@angular/core/test/view/view_def_spec.ts b/modules/@angular/core/test/view/view_def_spec.ts index e2f042c82a..fa9b24da5d 100644 --- a/modules/@angular/core/test/view/view_def_spec.ts +++ b/modules/@angular/core/test/view/view_def_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {NodeFlags, QueryValueType, ViewData, ViewDefinition, ViewFlags, anchorDef, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, directiveDef, elementDef, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {NodeFlags, QueryValueType, ViewData, ViewDefinition, ViewFlags, anchorDef, directiveDef, elementDef, textDef, viewDef} from '@angular/core/src/view/index'; export function main() { describe('viewDef', () => { diff --git a/modules/benchmarks/src/tree/ng2_next/tree.ts b/modules/benchmarks/src/tree/ng2_next/tree.ts index 88473fe844..aadd58da1b 100644 --- a/modules/benchmarks/src/tree/ng2_next/tree.ts +++ b/modules/benchmarks/src/tree/ng2_next/tree.ts @@ -8,7 +8,7 @@ import {NgIf} from '@angular/common'; import {Component, ComponentFactory, ComponentRef, Injector, NgModule, RootRenderer, Sanitizer, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; -import {BindingType, NodeFlags, ViewData, ViewDefinition, ViewFlags, anchorDef, checkNodeInline, createComponentFactory, directiveDef, elementDef, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {ArgumentType, BindingType, NodeFlags, ViewData, ViewDefinition, ViewFlags, anchorDef, createComponentFactory, directiveDef, elementDef, initServicesIfNeeded, textDef, viewDef} from '@angular/core/src/view/index'; import {DomSanitizerImpl, SafeStyle} from '@angular/platform-browser/src/security/dom_sanitization_service'; import {TreeNode, emptyTree} from '../util'; @@ -21,7 +21,7 @@ export class TreeComponent { get bgColor() { return this.data.depth % 2 ? trustedEmptyColor : trustedGreyColor; } } -let viewFlags = ViewFlags.DirectDom; +let viewFlags = ViewFlags.None; function TreeComponent_Host(): ViewDefinition { return viewDef(viewFlags, [ @@ -38,10 +38,9 @@ function TreeComponent_0(): ViewDefinition { directiveDef( NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null, TreeComponent_0), ], - (view: ViewData) => { + (check, view) => { const cmp = view.component; - setCurrentNode(view, 1); - checkNodeInline(cmp.data.left); + check(view, 1, ArgumentType.Inline, cmp.data.left); }); const TreeComponent_2: ViewDefinition = viewDef( @@ -51,10 +50,9 @@ function TreeComponent_0(): ViewDefinition { directiveDef( NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null, TreeComponent_0), ], - (view: ViewData) => { + (check, view) => { const cmp = view.component; - setCurrentNode(view, 1); - checkNodeInline(cmp.data.right); + check(view, 1, ArgumentType.Inline, cmp.data.left); }); return viewDef( @@ -71,16 +69,12 @@ function TreeComponent_0(): ViewDefinition { directiveDef( NodeFlags.None, null, 0, NgIf, [ViewContainerRef, TemplateRef], {ngIf: [0, 'ngIf']}), ], - (view: ViewData) => { + (check, view) => { const cmp = view.component; - setCurrentNode(view, 0); - checkNodeInline(cmp.bgColor); - setCurrentNode(view, 1); - checkNodeInline(cmp.data.value); - setCurrentNode(view, 3); - checkNodeInline(cmp.data.left != null); - setCurrentNode(view, 5); - checkNodeInline(cmp.data.right != null); + check(view, 0, ArgumentType.Inline, cmp.bgColor); + check(view, 1, ArgumentType.Inline, cmp.data.value); + check(view, 3, ArgumentType.Inline, cmp.data.left != null); + check(view, 5, ArgumentType.Inline, cmp.data.right != null); }); } @@ -90,10 +84,11 @@ export class AppModule implements Injector { componentRef: ComponentRef; constructor() { + initServicesIfNeeded(); this.sanitizer = new DomSanitizerImpl(); trustedEmptyColor = this.sanitizer.bypassSecurityTrustStyle(''); trustedGreyColor = this.sanitizer.bypassSecurityTrustStyle('grey'); - this.componentFactory = createComponentFactory('#root', TreeComponent_Host); + this.componentFactory = createComponentFactory('#root', TreeComponent, TreeComponent_Host); } get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any { @@ -106,6 +101,8 @@ export class AppModule implements Injector { return Injector.NULL.get(token, notFoundValue); } - bootstrap() { this.componentRef = this.componentFactory.create(this); } + bootstrap() { + this.componentRef = this.componentFactory.create(this, [], this.componentFactory.selector); + } tick() { this.componentRef.changeDetectorRef.detectChanges(); } }