diff --git a/modules/@angular/core/src/linker/component_factory.ts b/modules/@angular/core/src/linker/component_factory.ts index 7c464c1630..9b02424c4a 100644 --- a/modules/@angular/core/src/linker/component_factory.ts +++ b/modules/@angular/core/src/linker/component_factory.ts @@ -95,12 +95,10 @@ export class ComponentFactory { /** @internal */ _viewClass: Type>; constructor( - public selector: string, _viewClass: Type>, private _componentType: Type) { + public selector: string, _viewClass: Type>, public componentType: Type) { this._viewClass = _viewClass; } - get componentType(): Type { return this._componentType; } - /** * Creates a new component. */ diff --git a/modules/@angular/core/src/view/element.ts b/modules/@angular/core/src/view/element.ts index 7e3bda6452..829c86ef36 100644 --- a/modules/@angular/core/src/view/element.ts +++ b/modules/@angular/core/src/view/element.ts @@ -9,7 +9,7 @@ import {isDevMode} from '../application_ref'; import {SecurityContext} from '../security'; -import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, ViewData, ViewDefinition, ViewFlags, asElementData} from './types'; +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'; export function anchorDef( @@ -129,19 +129,30 @@ export function elementDef( } export function createElement(view: ViewData, renderHost: any, def: NodeDef): ElementData { - const parentNode = - def.parent != null ? asElementData(view, def.parent).renderElement : renderHost; const elDef = def.element; + const rootSelectorOrNode = view.root.selectorOrNode; let el: any; - if (view.renderer) { - const debugContext = - isDevMode() ? view.services.createDebugContext(view, def.index) : undefined; - el = elDef.name ? view.renderer.createElement(parentNode, elDef.name, debugContext) : - view.renderer.createTemplateAnchor(parentNode, debugContext); + if (view.parent || !rootSelectorOrNode) { + 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); + } + } } else { - el = elDef.name ? document.createElement(elDef.name) : document.createComment(''); - if (parentNode) { - parentNode.appendChild(el); + 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 = ''; } } if (elDef.attrs) { @@ -269,7 +280,7 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu function setElementAttribute( view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) { const securityContext = binding.securityContext; - let renderValue = securityContext ? view.services.sanitize(securityContext, value) : value; + 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); @@ -296,7 +307,7 @@ function setElementClass(view: ViewData, renderNode: any, name: string, value: b function setElementStyle( view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) { - let renderValue = view.services.sanitize(SecurityContext.STYLE, value); + let renderValue = view.root.sanitizer.sanitize(SecurityContext.STYLE, value); if (renderValue != null) { renderValue = renderValue.toString(); const unit = binding.suffix; @@ -322,7 +333,7 @@ function setElementStyle( function setElementProperty( view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) { const securityContext = binding.securityContext; - let renderValue = securityContext ? view.services.sanitize(securityContext, value) : value; + 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) { diff --git a/modules/@angular/core/src/view/errors.ts b/modules/@angular/core/src/view/errors.ts index 0348821f02..7863e10974 100644 --- a/modules/@angular/core/src/view/errors.ts +++ b/modules/@angular/core/src/view/errors.ts @@ -35,7 +35,7 @@ export function viewDebugError(msg: string, context: DebugContext): ViewDebugErr const err = new Error(msg) as any; err.context = context; err.stack = context.source; - context.view.state = ViewState.Errored; + context.view.state |= ViewState.Errored; return err; } diff --git a/modules/@angular/core/src/view/index.ts b/modules/@angular/core/src/view/index.ts index 291ccc30ae..0e2a25ea9a 100644 --- a/modules/@angular/core/src/view/index.ts +++ b/modules/@angular/core/src/view/index.ts @@ -14,7 +14,13 @@ export {queryDef} from './query'; export {textDef} from './text'; export {rootRenderNodes, setCurrentNode} from './util'; export {checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createEmbeddedView, createRootView, destroyView, viewDef} from './view'; -export {attachEmbeddedView, detachEmbeddedView} from './view_attach'; - +export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; export * from './types'; -export {DefaultServices} from './services'; + +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/provider.ts b/modules/@angular/core/src/view/provider.ts index caf97fc5c0..ce62ce6b1d 100644 --- a/modules/@angular/core/src/view/provider.ts +++ b/modules/@angular/core/src/view/provider.ts @@ -17,8 +17,8 @@ import {Renderer} 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, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types'; -import {checkAndUpdateBinding, dispatchEvent, entryAction, findElementDef, setBindingDebugInfo, setCurrentNode, unwrapValue} from './util'; +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'; const _tokenKeyCache = new Map(); @@ -169,7 +169,7 @@ export function checkAndUpdateProviderInline( if (changes) { provider.ngOnChanges(changes); } - if (view.state === ViewState.FirstCheck && (def.flags & NodeFlags.OnInit)) { + if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) { provider.ngOnInit(); } if (def.flags & NodeFlags.DoCheck) { @@ -186,7 +186,7 @@ export function checkAndUpdateProviderDynamic(view: ViewData, def: NodeDef, valu if (changes) { provider.ngOnChanges(changes); } - if (view.state === ViewState.FirstCheck && (def.flags & NodeFlags.OnInit)) { + if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) { provider.ngOnInit(); } if (def.flags & NodeFlags.DoCheck) { @@ -290,16 +290,18 @@ function callFactory( } export function resolveDep( - view: ViewData, requestNodeIndex: number, elIndex: number, depDef: DepDef): any { - const notFoundValue = depDef.flags & DepFlags.Optional ? null : Injector.THROW_IF_NOT_FOUND; + view: ViewData, requestNodeIndex: number, elIndex: number, depDef: DepDef, + notFoundValue = Injector.THROW_IF_NOT_FOUND): any { + const startView = view; + if (depDef.flags & DepFlags.Optional) { + notFoundValue = null; + } const tokenKey = depDef.tokenKey; if (depDef.flags & DepFlags.SkipSelf) { requestNodeIndex = null; - const elDef = view.def.nodes[elIndex]; - if (elDef.parent != null) { - elIndex = elDef.parent; - } else { + elIndex = view.def.nodes[elIndex].parent; + while (elIndex == null && view) { elIndex = parentDiIndex(view); view = view.parent; } @@ -317,9 +319,9 @@ export function resolveDep( case ElementRefTokenKey: return new ElementRef(asElementData(view, elIndex).renderElement); case ViewContainerRefTokenKey: - return view.services.createViewContainerRef(asElementData(view, elIndex)); + return Refs.createViewContainerRef(view, elIndex); case TemplateRefTokenKey: - return view.services.createTemplateRef(view, elDef); + return Refs.createTemplateRef(view, elDef); case ChangeDetectorRefTokenKey: let cdView = view; // If we are still checking dependencies on the initial element... @@ -330,9 +332,9 @@ export function resolveDep( } } // A ViewRef is also a ChangeDetectorRef - return view.services.createViewRef(cdView); + return Refs.createViewRef(cdView); case InjectorRefTokenKey: - return createInjector(view, elIndex); + return Refs.createInjector(view, elIndex); default: const providerIndex = elDef.element.providerIndices[tokenKey]; if (providerIndex != null) { @@ -347,34 +349,7 @@ export function resolveDep( elIndex = parentDiIndex(view); view = view.parent; } - return Injector.NULL.get(depDef.token, notFoundValue); -} - -/** - * for component views, this is the same as parentIndex. - * for embedded views, this is the index of the parent node - * that contains the view container. - */ -function parentDiIndex(view: ViewData): number { - if (view.parent) { - const parentNodeDef = view.def.nodes[view.parentIndex]; - return parentNodeDef.element && parentNodeDef.element.template ? parentNodeDef.parent : - parentNodeDef.index; - } - return view.parentIndex; -} - -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): any { - return resolveDep( - this.view, undefined, this.elIndex, - {flags: DepFlags.None, token, tokenKey: tokenKey(token)}); - } + return startView.root.injector.get(depDef.token, notFoundValue); } function checkAndUpdateProp( @@ -385,8 +360,9 @@ function checkAndUpdateProp( if (def.flags & NodeFlags.OnChanges) { const oldValue = view.oldValues[def.bindingIndex + bindingIdx]; changed = checkAndUpdateBinding(view, def, bindingIdx, value); - change = - changed ? new SimpleChange(oldValue, value, view.state === ViewState.FirstCheck) : null; + change = changed ? + new SimpleChange(oldValue, value, (view.state & ViewState.FirstCheck) !== 0) : + null; } else { changed = checkAndUpdateBinding(view, def, bindingIdx, value); } diff --git a/modules/@angular/core/src/view/query.ts b/modules/@angular/core/src/view/query.ts index e8a3afc8ed..134032f1e3 100644 --- a/modules/@angular/core/src/view/query.ts +++ b/modules/@angular/core/src/view/query.ts @@ -11,7 +11,7 @@ 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, ViewData, asElementData, asProviderData, asQueryList} from './types'; +import {NodeDef, NodeFlags, NodeType, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Refs, ViewData, asElementData, asProviderData, asQueryList} from './types'; import {declaredViewContainer} from './util'; export function queryDef( @@ -158,10 +158,10 @@ export function getQueryValue(view: ViewData, nodeDef: NodeDef, queryId: string) value = new ElementRef(asElementData(view, nodeDef.index).renderElement); break; case QueryValueType.TemplateRef: - value = view.services.createTemplateRef(view, nodeDef); + value = Refs.createTemplateRef(view, nodeDef); break; case QueryValueType.ViewContainerRef: - value = view.services.createViewContainerRef(asElementData(view, nodeDef.index)); + value = Refs.createViewContainerRef(view, nodeDef.index); break; case QueryValueType.Provider: value = asProviderData(view, nodeDef.index).instance; diff --git a/modules/@angular/core/src/view/services.ts b/modules/@angular/core/src/view/refs.ts similarity index 51% rename from modules/@angular/core/src/view/services.ts rename to modules/@angular/core/src/view/refs.ts index 41b85cfcb2..6bb5eee15b 100644 --- a/modules/@angular/core/src/view/services.ts +++ b/modules/@angular/core/src/view/refs.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ +import {ChangeDetectorRef} from '../change_detection/change_detection'; import {Injectable, Injector} from '../di'; -import {unimplemented} from '../facade/errors'; import {ComponentFactory, ComponentRef} from '../linker/component_factory'; import {ElementRef} from '../linker/element_ref'; import {TemplateRef} from '../linker/template_ref'; @@ -15,44 +15,126 @@ import {ViewContainerRef} from '../linker/view_container_ref'; import {EmbeddedViewRef, ViewRef} from '../linker/view_ref'; import {RenderComponentType, Renderer, RootRenderer} from '../render/api'; import {Sanitizer, SecurityContext} from '../security'; +import {Type} from '../type'; -import {createInjector} from './provider'; +import {resolveDep, tokenKey} from './provider'; import {getQueryValue} from './query'; -import {DebugContext, ElementData, NodeData, NodeDef, NodeType, Services, ViewData, ViewDefinition, ViewState, asElementData} from './types'; -import {findElementDef, isComponentView, renderNode, rootRenderNodes} from './util'; -import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, destroyView} from './view'; -import {attachEmbeddedView, detachEmbeddedView} from './view_attach'; +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'; -@Injectable() -export class DefaultServices implements Services { - constructor(private _rootRenderer: RootRenderer, private _sanitizer: Sanitizer) {} +const EMPTY_CONTEXT = new Object(); - renderComponent(rcp: RenderComponentType): Renderer { - return this._rootRenderer.renderComponent(rcp); - } - sanitize(context: SecurityContext, value: string): string { - return this._sanitizer.sanitize(context, value); +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(data: ElementData): ViewContainerRef { - return new ViewContainerRef_(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); } } +class ComponentFactory_ implements ComponentFactory { + /** + * Only needed so that we can implement ComponentFactory + * @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; + } + + /** + * Creates a new component. + */ + create( + injector: Injector, projectableNodes: any[][] = null, + rootSelectorOrNode: string|any = null): ComponentRef { + if (!projectableNodes) { + projectableNodes = []; + } + if (!rootSelectorOrNode) { + rootSelectorOrNode = this.selector; + } + 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); + } +} + +class ComponentRef_ implements ComponentRef { + private _viewRef: ViewRef_; + constructor(private _view: ViewData, private _component: any) { + this._viewRef = new ViewRef_(_view); + } + 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; }; + get hostView(): ViewRef { return this._viewRef; }; + get changeDetectorRef(): ChangeDetectorRef { return this._viewRef; }; + get componentType(): Type { return this._component.constructor; } + + destroy(): void { this._viewRef.destroy(); } + onDestroy(callback: Function): void { this._viewRef.onDestroy(callback); } +} + class ViewContainerRef_ implements ViewContainerRef { - constructor(private _data: ElementData) {} + private _data: ElementData; + constructor(private _view: ViewData, private _elIndex: number) { + this._data = asElementData(_view, _elIndex); + } - get element(): ElementRef { return unimplemented(); } + get element(): ElementRef { return new ElementRef(this._data.renderElement); } - get injector(): Injector { return unimplemented(); } + get injector(): Injector { return new Injector_(this._view, this._elIndex); } - get parentInjector(): Injector { return unimplemented(); } + get parentInjector(): Injector { + let view = this._view; + let elIndex = view.def.nodes[this._elIndex].parent; + while (elIndex == null && view) { + elIndex = parentDiIndex(view); + view = view.parent; + } + return view ? new Injector_(view, elIndex) : this._view.root.injector; + } clear(): void { const len = this._data.embeddedViews.length; @@ -76,7 +158,10 @@ class ViewContainerRef_ implements ViewContainerRef { createComponent( componentFactory: ComponentFactory, index?: number, injector?: Injector, projectableNodes?: any[][]): ComponentRef { - return unimplemented(); + const contextInjector = injector || this.parentInjector; + const componentRef = componentFactory.create(contextInjector, projectableNodes); + this.insert(componentRef.hostView, index); + return componentRef; } insert(viewRef: ViewRef, index?: number): ViewRef { @@ -85,7 +170,11 @@ class ViewContainerRef_ implements ViewContainerRef { return viewRef; } - move(viewRef: ViewRef, currentIndex: number): ViewRef { return unimplemented(); } + move(viewRef: ViewRef_, currentIndex: number): ViewRef { + const previousIndex = this._data.embeddedViews.indexOf(viewRef._view); + moveEmbeddedView(this._data, previousIndex, currentIndex); + return viewRef; + } indexOf(viewRef: ViewRef): number { return this._data.embeddedViews.indexOf((viewRef)._view); @@ -113,33 +202,17 @@ class ViewRef_ implements EmbeddedViewRef { get context() { return this._view.context; } - get destroyed(): boolean { return this._view.state === ViewState.Destroyed; } + get destroyed(): boolean { return (this._view.state & ViewState.Destroyed) !== 0; } markForCheck(): void { this.reattach(); } - detach(): void { - if (this._view.state === ViewState.ChecksEnabled) { - this._view.state = ViewState.ChecksDisabled; - } - } - detectChanges(): void { - if (this._view.state !== ViewState.FirstCheck) { - checkAndUpdateView(this._view); - } - } - checkNoChanges(): void { - if (this._view.state !== ViewState.FirstCheck) { - checkNoChangesView(this._view); - } - } + detach(): void { this._view.state &= ~ViewState.ChecksEnabled; } + detectChanges(): void { checkAndUpdateView(this._view); } + checkNoChanges(): void { checkNoChangesView(this._view); } - reattach(): void { - if (this._view.state === ViewState.ChecksDisabled) { - this._view.state = ViewState.ChecksEnabled; - } - } - onDestroy(callback: Function) { unimplemented(); } + reattach(): void { this._view.state |= ViewState.ChecksEnabled; } + onDestroy(callback: Function) { this._view.disposables.push(callback); } - destroy() { unimplemented(); } + destroy() { destroyView(this._view); } } class TemplateRef_ implements TemplateRef { @@ -154,6 +227,15 @@ class TemplateRef_ implements TemplateRef { } } +class Injector_ implements Injector { + constructor(private view: ViewData, private elIndex: number) {} + get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any { + return resolveDep( + this.view, undefined, this.elIndex, + {flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue); + } +} + class DebugContext_ implements DebugContext { private nodeDef: NodeDef; private elDef: NodeDef; @@ -165,7 +247,7 @@ class DebugContext_ implements DebugContext { this.nodeDef = view.def.nodes[nodeIndex]; this.elDef = findElementDef(view, nodeIndex); } - get injector(): Injector { return createInjector(this.view, this.elDef.index); } + get injector(): Injector { return new Injector_(this.view, this.elDef.index); } get component(): any { return this.view.component; } get providerTokens(): any[] { const tokens: any[] = []; diff --git a/modules/@angular/core/src/view/text.ts b/modules/@angular/core/src/view/text.ts index df273216e4..ef4cb526df 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, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types'; +import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, Refs, RootData, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types'; import {checkAndUpdateBinding, sliceErrorStack, unwrapValue} from './util'; export function textDef(ngContentIndex: number, constants: string[]): NodeDef { @@ -54,8 +54,7 @@ export function createText(view: ViewData, renderHost: any, def: NodeDef): TextD def.parent != null ? asElementData(view, def.parent).renderElement : renderHost; let renderNode: any; if (view.renderer) { - const debugContext = - isDevMode() ? view.services.createDebugContext(view, def.index) : undefined; + 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); diff --git a/modules/@angular/core/src/view/types.ts b/modules/@angular/core/src/view/types.ts index 22fc7e59ce..f8faf32c16 100644 --- a/modules/@angular/core/src/view/types.ts +++ b/modules/@angular/core/src/view/types.ts @@ -7,6 +7,8 @@ */ import {PipeTransform} from '../change_detection/change_detection'; +import {Injector} from '../di'; +import {ComponentFactory} from '../linker/component_factory'; import {QueryList} from '../linker/query_list'; import {TemplateRef} from '../linker/template_ref'; import {ViewContainerRef} from '../linker/view_container_ref'; @@ -264,7 +266,7 @@ export interface NgContentDef { export interface ViewData { def: ViewDefinition; renderer: Renderer; - services: Services; + root: RootData; // index of parent element / anchor. Not the index // of the provider with the component view. parentIndex: number; @@ -282,12 +284,14 @@ export interface ViewData { disposables: DisposableFn[]; } +/** + * Bitmask of states + */ export enum ViewState { - FirstCheck, - ChecksEnabled, - ChecksDisabled, - Errored, - Destroyed + FirstCheck = 1 << 0, + ChecksEnabled = 1 << 1, + Errored = 1 << 2, + Destroyed = 1 << 3 } export type DisposableFn = () => void; @@ -382,17 +386,12 @@ export function asQueryList(view: ViewData, index: number): QueryList { return view.nodes[index]; } -export interface Services { - renderComponent(rcp: RenderComponentType): Renderer; - sanitize(context: SecurityContext, value: string): string; - // Note: This needs to be here to prevent a cycle in source files. - createViewRef(data: ViewData): ViewRef; - // Note: This needs to be here to prevent a cycle in source files. - createViewContainerRef(data: ElementData): ViewContainerRef; - // Note: This needs to be here to prevent a cycle in source files. - createTemplateRef(parentView: ViewData, def: NodeDef): TemplateRef; - // Note: This needs to be here to prevent a cycle in source files. - createDebugContext(view: ViewData, nodeIndex: number): DebugContext; +export interface RootData { + injector: Injector; + projectableNodes: any[][]; + selectorOrNode: string|any; + renderer: RootRenderer; + sanitizer: Sanitizer; } // ------------------------------------- @@ -412,3 +411,37 @@ export interface DebugContext extends RenderDebugInfo { componentRenderElement: any; renderNode: any; } + +/** + * This class is used to prevent cycles in the source files. + */ +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; +} diff --git a/modules/@angular/core/src/view/util.ts b/modules/@angular/core/src/view/util.ts index 307c2e8e76..6a42c6e34b 100644 --- a/modules/@angular/core/src/view/util.ts +++ b/modules/@angular/core/src/view/util.ts @@ -9,11 +9,15 @@ 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 {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 {ElementData, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types'; +import {DebugContext, ElementData, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, Refs, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types'; export function setBindingDebugInfo( renderer: Renderer, renderNode: any, propName: string, value: any) { @@ -36,23 +40,23 @@ function camelCaseToDashCase(input: string): string { export function checkBindingNoChanges( view: ViewData, def: NodeDef, bindingIdx: number, value: any) { const oldValue = view.oldValues[def.bindingIndex + bindingIdx]; - if (view.state === ViewState.FirstCheck || !devModeEqual(oldValue, value)) { + if ((view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value)) { throw expressionChangedAfterItHasBeenCheckedError( - view.services.createDebugContext(view, def.index), oldValue, value, - view.state === ViewState.FirstCheck); + Refs.createDebugContext(view, def.index), oldValue, value, + (view.state & ViewState.FirstCheck) !== 0); } } export function checkAndUpdateBinding( view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean { const oldValues = view.oldValues; - if (view.state === ViewState.FirstCheck || + if ((view.state & ViewState.FirstCheck) || !looseIdentical(oldValues[def.bindingIndex + bindingIdx], value)) { oldValues[def.bindingIndex + bindingIdx] = value; if (def.flags & NodeFlags.HasComponent) { const compView = asProviderData(view, def.index).componentView; - if (compView.state === ViewState.ChecksDisabled && compView.def.flags & ViewFlags.OnPush) { - compView.state = ViewState.ChecksEnabled; + if (compView.def.flags & ViewFlags.OnPush) { + compView.state |= ViewState.ChecksEnabled; } } return true; @@ -65,8 +69,8 @@ export function dispatchEvent( setCurrentNode(view, nodeIndex); let currView = view; while (currView) { - if (currView.state === ViewState.ChecksDisabled && currView.def.flags & ViewFlags.OnPush) { - currView.state = ViewState.ChecksEnabled; + if (currView.def.flags & ViewFlags.OnPush) { + currView.state |= ViewState.ChecksEnabled; } currView = currView.parent; } @@ -88,6 +92,20 @@ export function declaredViewContainer(view: ViewData): ElementData { return undefined; } +/** + * for component views, this is the same as parentIndex. + * for embedded views, this is the index of the parent node + * that contains the view container. + */ +export function parentDiIndex(view: ViewData): number { + if (view.parent) { + const parentNodeDef = view.def.nodes[view.parentIndex]; + return parentNodeDef.element && parentNodeDef.element.template ? parentNodeDef.parent : + parentNodeDef.index; + } + return view.parentIndex; +} + export function findElementDef(view: ViewData, nodeIndex: number): NodeDef { const viewDef = view.def; let nodeDef = viewDef.nodes[nodeIndex]; @@ -163,7 +181,7 @@ export function currentAction() { * 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) { + if (view.state & ViewState.Destroyed) { throw viewDestroyedError(_currentAction); } _currentView = view; @@ -198,7 +216,7 @@ function callWithTryCatch(fn: (a: any) => any, arg: any): any { if (isViewDebugError(e) || !_currentView) { throw e; } - const debugContext = _currentView.services.createDebugContext(_currentView, _currentNodeIndex); + const debugContext = Refs.createDebugContext(_currentView, _currentNodeIndex); throw viewWrappedDebugError(e, debugContext); } } @@ -231,7 +249,7 @@ export function visitProjectedRenderNodes( view: ViewData, ngContentIndex: number, action: RenderNodeAction, parentNode: any, nextSibling: any, target: any[]) { let compView = view; - while (!isComponentView(compView)) { + while (compView && !isComponentView(compView)) { compView = compView.parent; } const hostView = compView.parent; @@ -246,6 +264,15 @@ export function visitProjectedRenderNodes( // jump to next sibling i += nodeDef.childCount; } + if (!hostView.parent) { + // a root view + const projectedNodes = view.root.projectableNodes[ngContentIndex]; + if (projectedNodes) { + for (let i = 0; i < projectedNodes.length; i++) { + execRenderNodeAction(projectedNodes[i], action, parentNode, nextSibling, target); + } + } + } } function visitRenderNode( @@ -256,20 +283,7 @@ function visitRenderNode( view, nodeDef.ngContent.index, action, parentNode, nextSibling, target); } else { const rn = renderNode(view, nodeDef); - switch (action) { - case RenderNodeAction.AppendChild: - parentNode.appendChild(rn); - break; - case RenderNodeAction.InsertBefore: - parentNode.insertBefore(rn, nextSibling); - break; - case RenderNodeAction.RemoveChild: - parentNode.removeChild(rn); - break; - case RenderNodeAction.Collect: - target.push(rn); - break; - } + execRenderNodeAction(rn, action, parentNode, nextSibling, target); if (nodeDef.flags & NodeFlags.HasEmbeddedViews) { const embeddedViews = asElementData(view, nodeDef.index).embeddedViews; if (embeddedViews) { @@ -280,3 +294,21 @@ function visitRenderNode( } } } + +function execRenderNodeAction( + renderNode: any, action: RenderNodeAction, parentNode: any, nextSibling: any, target: any[]) { + switch (action) { + case RenderNodeAction.AppendChild: + parentNode.appendChild(renderNode); + break; + case RenderNodeAction.InsertBefore: + parentNode.insertBefore(renderNode, nextSibling); + break; + case RenderNodeAction.RemoveChild: + parentNode.removeChild(renderNode); + break; + case RenderNodeAction.Collect: + target.push(renderNode); + break; + } +} diff --git a/modules/@angular/core/src/view/view.ts b/modules/@angular/core/src/view/view.ts index 59a378b9c1..d25e2302b5 100644 --- a/modules/@angular/core/src/view/view.ts +++ b/modules/@angular/core/src/view/view.ts @@ -16,7 +16,7 @@ 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, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types'; +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'; const NOOP = (): any => undefined; @@ -224,32 +224,28 @@ function cloneAndModifyElement( export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData { // embedded views are seen as siblings to the anchor, so we need // to get the parent of the anchor and use it as parentIndex. - const view = createView(parent.services, parent, anchorDef.index, anchorDef.element.template); + const view = createView(parent.root, parent, anchorDef.index, anchorDef.element.template); initView(view, parent.component, context); createViewNodes(view); return view; } -/** - * We take in a ViewDefinitionFactory, so that we can initialize the debug/prod mode first, - * and then know whether to capture error stacks in ElementDefs. - */ -export function createRootView( - services: Services, defFactory: ViewDefinitionFactory, context?: any): ViewData { - const view = createView(services, null, null, resolveViewDefinition(defFactory)); +export function createRootView(root: RootData, def: ViewDefinition, context?: any): ViewData { + const view = createView(root, null, null, def); initView(view, context, context); createViewNodes(view); return view; } function createView( - services: Services, parent: ViewData, parentIndex: number, def: ViewDefinition): ViewData { + 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 ? services.renderComponent(def.componentType) : parent.renderer; + renderer = + def.componentType ? root.renderer.renderComponent(def.componentType) : parent.renderer; } const disposables = def.disposableCount ? new Array(def.disposableCount) : undefined; const view: ViewData = { @@ -258,7 +254,7 @@ function createView( parentIndex, context: undefined, component: undefined, nodes, - state: ViewState.FirstCheck, renderer, services, + state: ViewState.FirstCheck | ViewState.ChecksEnabled, renderer, root, oldValues: new Array(def.bindingCount), disposables }; return view; @@ -301,8 +297,7 @@ function _createViewNodes(view: ViewData) { // the component view. Therefore, we create the component view first // and set the ProviderData in ViewData, and then instantiate the provider. const componentView = createView( - view.services, view, nodeDef.parent, - resolveViewDefinition(nodeDef.provider.component)); + view.root, view, nodeDef.parent, resolveViewDefinition(nodeDef.provider.component)); const providerData = {componentView, instance: undefined}; nodes[i] = providerData as any; const instance = providerData.instance = createProviderInstance(view, nodeDef); @@ -352,21 +347,18 @@ function _checkAndUpdateView(view: ViewData) { callLifecycleHooksChildrenFirst( view, NodeFlags.AfterContentChecked | - (view.state === ViewState.FirstCheck ? NodeFlags.AfterContentInit : 0)); + (view.state & ViewState.FirstCheck ? NodeFlags.AfterContentInit : 0)); execComponentViewsAction(view, ViewAction.CheckAndUpdate); execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckAndUpdate); callLifecycleHooksChildrenFirst( view, NodeFlags.AfterViewChecked | - (view.state === ViewState.FirstCheck ? NodeFlags.AfterViewInit : 0)); + (view.state & ViewState.FirstCheck ? NodeFlags.AfterViewInit : 0)); - if (view.state === ViewState.FirstCheck || view.state === ViewState.ChecksEnabled) { - if (view.def.flags & ViewFlags.OnPush) { - view.state = ViewState.ChecksDisabled; - } else { - view.state = ViewState.ChecksEnabled; - } + if (view.def.flags & ViewFlags.OnPush) { + view.state &= ~ViewState.ChecksEnabled; } + view.state &= ~ViewState.FirstCheck; } export function checkNodeInline( @@ -476,9 +468,8 @@ function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) { const queryList = asQueryList(view, nodeDef.index); if (queryList.dirty) { throw expressionChangedAfterItHasBeenCheckedError( - view.services.createDebugContext(view, nodeDef.index), - `Query ${nodeDef.query.id} not dirty`, `Query ${nodeDef.query.id} dirty`, - view.state === ViewState.FirstCheck); + Refs.createDebugContext(view, nodeDef.index), `Query ${nodeDef.query.id} not dirty`, + `Query ${nodeDef.query.id} dirty`, (view.state & ViewState.FirstCheck) !== 0); } } @@ -493,7 +484,7 @@ function _destroyView(view: ViewData) { } execComponentViewsAction(view, ViewAction.Destroy); execEmbeddedViewsAction(view, ViewAction.Destroy); - view.state = ViewState.Destroyed; + view.state |= ViewState.Destroyed; } enum ViewAction { @@ -548,14 +539,17 @@ function execEmbeddedViewsAction(view: ViewData, action: ViewAction) { } function callViewAction(view: ViewData, action: ViewAction) { + const viewState = view.state; switch (action) { case ViewAction.CheckNoChanges: - if (view.state === ViewState.ChecksEnabled || view.state === ViewState.FirstCheck) { + if ((viewState & ViewState.ChecksEnabled) && + (viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) { _checkNoChangesView(view); } break; case ViewAction.CheckAndUpdate: - if (view.state === ViewState.ChecksEnabled || view.state === ViewState.FirstCheck) { + if ((viewState & ViewState.ChecksEnabled) && + (viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) { _checkAndUpdateView(view); } break; diff --git a/modules/@angular/core/src/view/view_attach.ts b/modules/@angular/core/src/view/view_attach.ts index 6376f47d29..996f5d8270 100644 --- a/modules/@angular/core/src/view/view_attach.ts +++ b/modules/@angular/core/src/view/view_attach.ts @@ -29,20 +29,8 @@ export function attachEmbeddedView(elementData: ElementData, viewIndex: number, dirtyParentQuery(queryId, view); } - // update rendering const prevView = viewIndex > 0 ? embeddedViews[viewIndex - 1] : null; - 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); - } - } + renderAttachEmbeddedView(elementData, prevView, view); } export function detachEmbeddedView(elementData: ElementData, viewIndex: number): ViewData { @@ -63,7 +51,51 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex: number): dirtyParentQuery(queryId, view); } - // update rendering + renderDetachEmbeddedView(elementData, view); + + return view; +} + +export function moveEmbeddedView( + elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData { + const embeddedViews = elementData.embeddedViews; + const view = embeddedViews[oldViewIndex]; + removeFromArray(embeddedViews, oldViewIndex); + if (newViewIndex == null) { + newViewIndex = embeddedViews.length; + } + addToArray(embeddedViews, newViewIndex, view); + + // Note: Don't need to change projectedViews as the order in there + // as always invalid... + + for (let queryId in view.def.nodeMatchedQueries) { + dirtyParentQuery(queryId, view); + } + + renderDetachEmbeddedView(elementData, view); + const prevView = newViewIndex > 0 ? embeddedViews[newViewIndex - 1] : null; + renderAttachEmbeddedView(elementData, prevView, view); + + return view; +} + +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); + } + } +} + +function renderDetachEmbeddedView(elementData: ElementData, view: ViewData) { if (view.renderer) { view.renderer.detachView(rootRenderNodes(view)); } else { @@ -72,7 +104,6 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex: number): visitRootRenderNodes(view, RenderNodeAction.RemoveChild, parentNode, null, undefined); } } - return view; } 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 84364f30d5..3624f4a6c5 100644 --- a/modules/@angular/core/test/view/anchor_spec.ts +++ b/modules/@angular/core/test/view/anchor_spec.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, getDebugNode} from '@angular/core'; -import {DebugContext, DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, elementDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +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 {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {isBrowser, setupAndCheckRenderer} from './helper'; +import {createRootData, isBrowser, setupAndCheckRenderer} from './helper'; export function main() { if (isBrowser()) { @@ -24,15 +24,14 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe(`View Anchor, directDom: ${config.directDom}`, () => { setupAndCheckRenderer(config); - let services: Services; + let rootData: RootData; let renderComponentType: RenderComponentType; - beforeEach( - inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => { - services = new DefaultServices(rootRenderer, sanitizer); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - })); + beforeEach(() => { + rootData = createRootData(); + renderComponentType = + new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); + }); function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { @@ -41,7 +40,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { function createAndGetRootNodes( viewDef: ViewDefinition, ctx?: any): {rootNodes: any[], view: ViewData} { - const view = createRootView(services, () => viewDef, ctx); + const view = createRootView(rootData, viewDef, ctx); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } diff --git a/modules/@angular/core/test/view/component_view_spec.ts b/modules/@angular/core/test/view/component_view_spec.ts index 65f6678706..44470b0ed9 100644 --- a/modules/@angular/core/test/view/component_view_spec.ts +++ b/modules/@angular/core/test/view/component_view_spec.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {BindingType, DefaultServices, NodeDef, NodeFlags, Services, 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 {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 {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {isBrowser, setupAndCheckRenderer} from './helper'; +import {createRootData, isBrowser, removeNodes, setupAndCheckRenderer} from './helper'; export function main() { if (isBrowser()) { @@ -24,15 +24,14 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe(`Component Views, directDom: ${config.directDom}`, () => { setupAndCheckRenderer(config); - let services: Services; + let rootData: RootData; let renderComponentType: RenderComponentType; - beforeEach( - inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => { - services = new DefaultServices(rootRenderer, sanitizer); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - })); + beforeEach(() => { + rootData = createRootData(); + renderComponentType = + new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); + }); function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, @@ -41,7 +40,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { - const view = createRootView(services, () => viewDef); + const view = createRootView(rootData, viewDef); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -70,6 +69,54 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(getDOM().nodeName(compRootEl).toLowerCase()).toBe('span'); }); + if (isBrowser()) { + describe('root views', () => { + let rootNode: HTMLElement; + beforeEach(() => { + rootNode = document.createElement('root'); + document.body.appendChild(rootNode); + removeNodes.push(rootNode); + }); + + 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 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 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'}), + ])); + 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'}), + ])); + expect(rootNode.childNodes.length).toBe(0); + }); + }); + } + describe('data binding', () => { it('should dirty check component views', () => { let value: any; @@ -132,11 +179,11 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { checkAndUpdateView(view); update.calls.reset(); - compView.state = ViewState.ChecksDisabled; + compView.state &= ~ViewState.ChecksEnabled; checkAndUpdateView(view); expect(update).not.toHaveBeenCalled(); - compView.state = ViewState.ChecksEnabled; + compView.state |= ViewState.ChecksEnabled; checkAndUpdateView(view); expect(update).toHaveBeenCalled(); }); diff --git a/modules/@angular/core/test/view/element_spec.ts b/modules/@angular/core/test/view/element_spec.ts index ebe04337f1..0ac0107b85 100644 --- a/modules/@angular/core/test/view/element_spec.ts +++ b/modules/@angular/core/test/view/element_spec.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; -import {BindingType, DebugContext, DefaultServices, NodeDef, NodeFlags, Services, 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 {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; +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 {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, isBrowser, setupAndCheckRenderer} from './helper'; +import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, createRootData, isBrowser, removeNodes, setupAndCheckRenderer} from './helper'; export function main() { if (isBrowser()) { @@ -24,15 +24,14 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe(`View Elements, directDom: ${config.directDom}`, () => { setupAndCheckRenderer(config); - let services: Services; + let rootData: RootData; let renderComponentType: RenderComponentType; - beforeEach( - inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => { - services = new DefaultServices(rootRenderer, sanitizer); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - })); + beforeEach(() => { + rootData = createRootData(); + renderComponentType = + new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); + }); function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { @@ -41,7 +40,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { function createAndGetRootNodes( viewDef: ViewDefinition, context?: any): {rootNodes: any[], view: ViewData} { - const view = createRootView(services, () => viewDef, context); + const view = createRootView(rootData, viewDef, context); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -236,16 +235,6 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { if (isBrowser()) { describe('listen to DOM events', () => { - let removeNodes: Node[]; - beforeEach(() => { removeNodes = []; }); - afterEach(() => { - removeNodes.forEach((node) => { - if (node.parentNode) { - node.parentNode.removeChild(node); - } - }); - }); - function createAndAttachAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { const result = createAndGetRootNodes(viewDef); diff --git a/modules/@angular/core/test/view/embedded_view_spec.ts b/modules/@angular/core/test/view/embedded_view_spec.ts index 28994d0511..2f71cbe071 100644 --- a/modules/@angular/core/test/view/embedded_view_spec.ts +++ b/modules/@angular/core/test/view/embedded_view_spec.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {BindingType, DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, attachEmbeddedView, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createEmbeddedView, createRootView, destroyView, detachEmbeddedView, directiveDef, elementDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +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 {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {isBrowser, setupAndCheckRenderer} from './helper'; +import {createRootData, isBrowser, setupAndCheckRenderer} from './helper'; export function main() { if (isBrowser()) { @@ -24,15 +24,14 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe(`Embedded Views, directDom: ${config.directDom}`, () => { setupAndCheckRenderer(config); - let services: Services; + let rootData: RootData; let renderComponentType: RenderComponentType; - beforeEach( - inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => { - services = new DefaultServices(rootRenderer, sanitizer); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - })); + beforeEach(() => { + rootData = createRootData(); + renderComponentType = + new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); + }); function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { @@ -45,7 +44,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { function createAndGetRootNodes( viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} { - const view = createRootView(services, () => viewDef, context); + const view = createRootView(rootData, viewDef, context); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -78,26 +77,54 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child1'}) ])) ])); + const viewContainerData = asElementData(parentView, 1); const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]); - const childView1 = createEmbeddedView(parentView, parentView.def.nodes[2]); - const rootChildren = getDOM().childNodes(rootNodes[0]); - attachEmbeddedView(asElementData(parentView, 1), 0, childView0); - attachEmbeddedView(asElementData(parentView, 1), 1, childView1); + attachEmbeddedView(viewContainerData, 0, childView0); + attachEmbeddedView(viewContainerData, 1, childView1); // 2 anchors + 2 elements + const rootChildren = getDOM().childNodes(rootNodes[0]); expect(rootChildren.length).toBe(4); expect(getDOM().getAttribute(rootChildren[1], 'name')).toBe('child0'); expect(getDOM().getAttribute(rootChildren[2], 'name')).toBe('child1'); - detachEmbeddedView(asElementData(parentView, 1), 1); - detachEmbeddedView(asElementData(parentView, 1), 0); + detachEmbeddedView(viewContainerData, 1); + detachEmbeddedView(viewContainerData, 0); expect(getDOM().childNodes(rootNodes[0]).length).toBe(2); }); + it('should move embedded views', () => { + const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([ + elementDef(NodeFlags.None, null, null, 2, 'div'), + anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ + elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child0'}) + ])), + anchorDef(NodeFlags.None, null, null, 0, embeddedViewDef([ + elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child1'}) + ])) + ])); + const viewContainerData = asElementData(parentView, 1); + + const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]); + const childView1 = createEmbeddedView(parentView, parentView.def.nodes[2]); + + attachEmbeddedView(viewContainerData, 0, childView0); + attachEmbeddedView(viewContainerData, 1, childView1); + + moveEmbeddedView(viewContainerData, 0, 1); + + expect(viewContainerData.embeddedViews).toEqual([childView1, childView0]); + // 2 anchors + 2 elements + const rootChildren = getDOM().childNodes(rootNodes[0]); + expect(rootChildren.length).toBe(4); + expect(getDOM().getAttribute(rootChildren[1], 'name')).toBe('child1'); + expect(getDOM().getAttribute(rootChildren[2], 'name')).toBe('child0'); + }); + it('should include embedded views in root nodes', () => { const {view: parentView} = createAndGetRootNodes(compViewDef([ anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ diff --git a/modules/@angular/core/test/view/helper.ts b/modules/@angular/core/test/view/helper.ts index bd62e13324..7ae6c2008e 100644 --- a/modules/@angular/core/test/view/helper.ts +++ b/modules/@angular/core/test/view/helper.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {RootRenderer} from '@angular/core'; -import {checkNodeDynamic, checkNodeInline} from '@angular/core/src/view/index'; +import {Injector, RootRenderer, Sanitizer} from '@angular/core'; +import {RootData, checkNodeDynamic, checkNodeInline} from '@angular/core/src/view/index'; import {TestBed} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -51,3 +51,19 @@ export function checkNodeInlineOrDynamic(inlineDynamic: InlineDynamic, values: a 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 let removeNodes: Node[]; +beforeEach(() => { removeNodes = []; }); +afterEach(() => { removeNodes.forEach((node) => getDOM().remove(node)); }); diff --git a/modules/@angular/core/test/view/ng_content_spec.ts b/modules/@angular/core/test/view/ng_content_spec.ts index 7e87ace66f..ba8cb453ba 100644 --- a/modules/@angular/core/test/view/ng_content_spec.ts +++ b/modules/@angular/core/test/view/ng_content_spec.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core'; -import {DebugContext, DefaultServices, NodeDef, NodeFlags, Services, 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 {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 {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {isBrowser, setupAndCheckRenderer} from './helper'; +import {createRootData, isBrowser, setupAndCheckRenderer} from './helper'; export function main() { if (isBrowser()) { @@ -24,15 +24,14 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe(`View NgContent, directDom: ${config.directDom}`, () => { setupAndCheckRenderer(config); - let services: Services; + let rootData: RootData; let renderComponentType: RenderComponentType; - beforeEach( - inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => { - services = new DefaultServices(rootRenderer, sanitizer); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - })); + beforeEach(() => { + rootData = createRootData(); + renderComponentType = + new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); + }); function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { @@ -57,7 +56,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { function createAndGetRootNodes( viewDef: ViewDefinition, ctx?: any): {rootNodes: any[], view: ViewData} { - const view = createRootView(services, () => viewDef, ctx || {}); + const view = createRootView(rootData, viewDef, ctx || {}); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -136,5 +135,18 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { detachEmbeddedView(asElementData(componentView, 1), 0); expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0])).length).toBe(1); }); + + if (isBrowser()) { + it('should use root projectable nodes', () => { + rootData.projectableNodes = + [[document.createTextNode('a')], [document.createTextNode('b')]]; + + 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]); + }); + } }); } diff --git a/modules/@angular/core/test/view/provider_spec.ts b/modules/@angular/core/test/view/provider_spec.ts index 3b8a168624..a106550169 100644 --- a/modules/@angular/core/test/view/provider_spec.ts +++ b/modules/@angular/core/test/view/provider_spec.ts @@ -7,11 +7,11 @@ */ 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 {BindingType, DebugContext, DefaultServices, DepFlags, NodeDef, NodeFlags, ProviderType, Services, 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 {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 {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, isBrowser, setupAndCheckRenderer} from './helper'; +import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, createRootData, isBrowser, setupAndCheckRenderer} from './helper'; export function main() { if (isBrowser()) { @@ -24,15 +24,14 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe(`View Providers, directDom: ${config.directDom}`, () => { setupAndCheckRenderer(config); - let services: Services; + let rootData: RootData; let renderComponentType: RenderComponentType; - beforeEach( - inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => { - services = new DefaultServices(rootRenderer, sanitizer); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - })); + beforeEach(() => { + rootData = createRootData(); + renderComponentType = + new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); + }); function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { @@ -44,7 +43,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { - const view = createRootView(services, () => viewDef); + const view = createRootView(rootData, viewDef); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -229,6 +228,18 @@ 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']) + ])); + + expect(instance.dep).toBe('rootValue'); + expect(getSpy).toHaveBeenCalledWith('rootDep', Injector.THROW_IF_NOT_FOUND); + }); + describe('builtin tokens', () => { it('should inject ViewContainerRef', () => { createAndGetRootNodes(compViewDef([ diff --git a/modules/@angular/core/test/view/pure_expression_spec.ts b/modules/@angular/core/test/view/pure_expression_spec.ts index 5d8defe00c..dacd73a8a9 100644 --- a/modules/@angular/core/test/view/pure_expression_spec.ts +++ b/modules/@angular/core/test/view/pure_expression_spec.ts @@ -6,23 +6,22 @@ * found in the LICENSE file at https://angular.io/license */ -import {PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue} from '@angular/core'; -import {DefaultServices, NodeDef, NodeFlags, Services, 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 {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 {inject} from '@angular/core/testing'; -import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic} from './helper'; +import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, createRootData} from './helper'; export function main() { describe(`View Pure Expressions`, () => { - let services: Services; + let rootData: RootData; let renderComponentType: RenderComponentType; - beforeEach( - inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => { - services = new DefaultServices(rootRenderer, sanitizer); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - })); + beforeEach(() => { + rootData = createRootData(); + renderComponentType = + new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); + }); function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { @@ -30,7 +29,7 @@ export function main() { } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { - const view = createRootView(services, () => viewDef); + const view = createRootView(rootData, viewDef); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } diff --git a/modules/@angular/core/test/view/query_spec.ts b/modules/@angular/core/test/view/query_spec.ts index 7d8fc21442..b55a2f326f 100644 --- a/modules/@angular/core/test/view/query_spec.ts +++ b/modules/@angular/core/test/view/query_spec.ts @@ -6,22 +6,23 @@ * found in the LICENSE file at https://angular.io/license */ -import {ElementRef, QueryList, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core'; -import {BindingType, DebugContext, DefaultServices, NodeDef, NodeFlags, QueryBindingType, QueryValueType, Services, 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 {ElementRef, Injector, QueryList, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core'; +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 {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; +import {createRootData} from './helper'; + export function main() { describe(`Query Views`, () => { - let services: Services; + let rootData: RootData; let renderComponentType: RenderComponentType; - beforeEach( - inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => { - services = new DefaultServices(rootRenderer, sanitizer); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - })); + beforeEach(() => { + rootData = createRootData(); + renderComponentType = + new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); + }); function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { @@ -34,7 +35,7 @@ export function main() { function createAndGetRootNodes( viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} { - const view = createRootView(services, () => viewDef, context); + const view = createRootView(rootData, viewDef, context); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } diff --git a/modules/@angular/core/test/view/services_spec.ts b/modules/@angular/core/test/view/refs_spec.ts similarity index 71% rename from modules/@angular/core/test/view/services_spec.ts rename to modules/@angular/core/test/view/refs_spec.ts index 3495388b91..7c9abceb05 100644 --- a/modules/@angular/core/test/view/services_spec.ts +++ b/modules/@angular/core/test/view/refs_spec.ts @@ -6,24 +6,23 @@ * found in the LICENSE file at https://angular.io/license */ -import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, getDebugNode} from '@angular/core'; -import {DebugContext, DefaultServices, NodeDef, NodeFlags, QueryValueType, Services, 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 {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 {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {isBrowser, setupAndCheckRenderer} from './helper'; +import {createRootData, isBrowser, setupAndCheckRenderer} from './helper'; export function main() { - describe('View Services', () => { - let services: Services; + describe('View References', () => { + let rootData: RootData; let renderComponentType: RenderComponentType; - beforeEach( - inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => { - services = new DefaultServices(rootRenderer, sanitizer); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - })); + beforeEach(() => { + rootData = createRootData(); + renderComponentType = + new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); + }); function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { @@ -32,7 +31,7 @@ export function main() { function createAndGetRootNodes( viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} { - const view = createRootView(services, () => viewDef, context); + const view = createRootView(rootData, viewDef, context); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } @@ -59,7 +58,7 @@ export function main() { const view = createViewWithData(); const compView = asProviderData(view, 1).componentView; - const debugCtx = view.services.createDebugContext(compView, 0); + const debugCtx = Refs.createDebugContext(compView, 0); expect(debugCtx.componentRenderElement).toBe(asElementData(view, 0).renderElement); expect(debugCtx.renderNode).toBe(asElementData(compView, 0).renderElement); @@ -76,7 +75,7 @@ export function main() { const view = createViewWithData(); const compView = asProviderData(view, 1).componentView; - const debugCtx = view.services.createDebugContext(compView, 2); + const debugCtx = Refs.createDebugContext(compView, 2); expect(debugCtx.componentRenderElement).toBe(asElementData(view, 0).renderElement); expect(debugCtx.renderNode).toBe(asTextData(compView, 2).renderText); @@ -90,7 +89,7 @@ export function main() { const view = createViewWithData(); const compView = asProviderData(view, 1).componentView; - const debugCtx = view.services.createDebugContext(compView, 1); + const debugCtx = Refs.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 4a766653c9..23a18f7475 100644 --- a/modules/@angular/core/test/view/text_spec.ts +++ b/modules/@angular/core/test/view/text_spec.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; -import {DebugContext, DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asTextData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, elementDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +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 {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, isBrowser, setupAndCheckRenderer} from './helper'; +import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, createRootData, isBrowser, setupAndCheckRenderer} from './helper'; export function main() { if (isBrowser()) { @@ -24,15 +24,14 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe(`View Text, directDom: ${config.directDom}`, () => { setupAndCheckRenderer(config); - let services: Services; + let rootData: RootData; let renderComponentType: RenderComponentType; - beforeEach( - inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => { - services = new DefaultServices(rootRenderer, sanitizer); - renderComponentType = - new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); - })); + beforeEach(() => { + rootData = createRootData(); + renderComponentType = + new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); + }); function compViewDef( nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { @@ -41,7 +40,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { function createAndGetRootNodes( viewDef: ViewDefinition, context?: any): {rootNodes: any[], view: ViewData} { - const view = createRootView(services, () => viewDef, context); + const view = createRootView(rootData, viewDef, context); const rootNodes = rootRenderNodes(view); return {rootNodes, view}; } diff --git a/modules/benchmarks/src/tree/ng2_next/index.ts b/modules/benchmarks/src/tree/ng2_next/index.ts index 7585c1dfaf..22c06c1d48 100644 --- a/modules/benchmarks/src/tree/ng2_next/index.ts +++ b/modules/benchmarks/src/tree/ng2_next/index.ts @@ -43,10 +43,7 @@ export function main() { enableProdMode(); appMod = new AppModule(); appMod.bootstrap(); - tree = appMod.rootComp; - const rootEl = document.querySelector('#root'); - rootEl.textContent = ''; - rootEl.appendChild(appMod.rootEl); + tree = appMod.componentRef.instance; bindAction('#destroyDom', destroyDom); bindAction('#createDom', createDom); diff --git a/modules/benchmarks/src/tree/ng2_next/tree.ts b/modules/benchmarks/src/tree/ng2_next/tree.ts index a2e4e05937..88473fe844 100644 --- a/modules/benchmarks/src/tree/ng2_next/tree.ts +++ b/modules/benchmarks/src/tree/ng2_next/tree.ts @@ -7,9 +7,9 @@ */ import {NgIf} from '@angular/common'; -import {Component, NgModule, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; -import {BindingType, DefaultServices, NodeFlags, ViewData, ViewDefinition, ViewFlags, anchorDef, asElementData, asProviderData, checkAndUpdateView, checkNodeInline, createRootView, directiveDef, elementDef, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; -import {DomSanitizer, DomSanitizerImpl, SafeStyle} from '@angular/platform-browser/src/security/dom_sanitization_service'; +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 {DomSanitizerImpl, SafeStyle} from '@angular/platform-browser/src/security/dom_sanitization_service'; import {TreeNode, emptyTree} from '../util'; @@ -84,21 +84,28 @@ function TreeComponent_0(): ViewDefinition { }); } -export class AppModule { - public rootComp: TreeComponent; - public rootEl: any; - private rootView: ViewData; - private sanitizer: DomSanitizer; +export class AppModule implements Injector { + private sanitizer: DomSanitizerImpl; + private componentFactory: ComponentFactory; + componentRef: ComponentRef; constructor() { this.sanitizer = new DomSanitizerImpl(); trustedEmptyColor = this.sanitizer.bypassSecurityTrustStyle(''); trustedGreyColor = this.sanitizer.bypassSecurityTrustStyle('grey'); + this.componentFactory = createComponentFactory('#root', TreeComponent_Host); } - bootstrap() { - this.rootView = createRootView(new DefaultServices(null, this.sanitizer), TreeComponent_Host); - this.rootComp = asProviderData(this.rootView, 1).instance; - this.rootEl = asElementData(this.rootView, 0).renderElement; + + get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any { + switch (token) { + case Sanitizer: + return this.sanitizer; + case RootRenderer: + return null; + } + return Injector.NULL.get(token, notFoundValue); } - tick() { checkAndUpdateView(this.rootView); } + + bootstrap() { this.componentRef = this.componentFactory.create(this); } + tick() { this.componentRef.changeDetectorRef.detectChanges(); } } diff --git a/tools/public_api_guard/core/index.d.ts b/tools/public_api_guard/core/index.d.ts index 56bfa79434..62c680ddcc 100644 --- a/tools/public_api_guard/core/index.d.ts +++ b/tools/public_api_guard/core/index.d.ts @@ -273,7 +273,7 @@ export interface ComponentDecorator { export declare class ComponentFactory { componentType: Type; selector: string; - constructor(selector: string, _viewClass: Type>, _componentType: Type); + constructor(selector: string, _viewClass: Type>, componentType: Type); create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string | any): ComponentRef; }