feat(core): view engine - integrate with ComponentFactory (#14237)

`ComponentFactory`s can now be created from a `ViewDefinitionFactory` via
`RefFactory.createComponentFactory`.

This commit also:
- splits `Services` into `Refs` and `RootData`
- changes `ViewState` into a bitmask
- implements `ViewContainerRef.move`

Part of #14013

PR Close #14237
This commit is contained in:
Tobias Bosch
2017-02-01 11:32:27 -08:00
committed by Miško Hevery
parent 388afa414e
commit 14d7844b2b
26 changed files with 618 additions and 353 deletions

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;

View File

@ -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<any, string>();
@ -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);
}

View File

@ -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;

View File

@ -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<any> {
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<any> {
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<any> {
/**
* 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<any> {
return this._viewDef.nodes[this._componentNodeIndex].provider.value;
}
/**
* Creates a new component.
*/
create(
injector: Injector, projectableNodes: any[][] = null,
rootSelectorOrNode: string|any = null): ComponentRef<any> {
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<any> {
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<any> { return <any>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 <ElementRef>unimplemented(); }
get element(): ElementRef { return new ElementRef(this._data.renderElement); }
get injector(): Injector { return <Injector>unimplemented(); }
get injector(): Injector { return new Injector_(this._view, this._elIndex); }
get parentInjector(): Injector { return <Injector>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<C>(
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
projectableNodes?: any[][]): ComponentRef<C> {
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_>viewRef)._view);
@ -113,33 +202,17 @@ class ViewRef_ implements EmbeddedViewRef<any> {
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(<any>callback); }
destroy() { unimplemented(); }
destroy() { destroyView(this._view); }
}
class TemplateRef_ implements TemplateRef<any> {
@ -154,6 +227,15 @@ class TemplateRef_ implements TemplateRef<any> {
}
}
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[] = [];

View File

@ -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);

View File

@ -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<any> {
return <any>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<any>;
// 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<any> {
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<any> {
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<any>;
abstract createViewRef(data: ViewData): ViewRef;
abstract createViewContainerRef(view: ViewData, elIndex: number): ViewContainerRef;
abstract createTemplateRef(parentView: ViewData, def: NodeDef): TemplateRef<any>;
abstract createInjector(view: ViewData, elIndex: number): Injector;
abstract createDebugContext(view: ViewData, nodeIndex: number): DebugContext;
}

View File

@ -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;
}
}

View File

@ -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 = <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;

View File

@ -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) {