feat(core): view engine - add debug information (#14197)
Creates debug information for the renderer, and also reports errors relative to the declaration place in the template. Part of #14013 PR Close #14197
This commit is contained in:

committed by
Miško Hevery

parent
c48dd76f5c
commit
52b21275f4
@ -6,10 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {SecurityContext} from '../security';
|
||||
|
||||
import {BindingDef, BindingType, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, ViewData, ViewDefinition, ViewFlags, asElementData} from './types';
|
||||
import {checkAndUpdateBinding, setBindingDebugInfo} from './util';
|
||||
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, ViewData, ViewDefinition, ViewFlags, asElementData} from './types';
|
||||
import {checkAndUpdateBinding, entryAction, setBindingDebugInfo, setCurrentNode, sliceErrorStack} from './util';
|
||||
|
||||
export function anchorDef(
|
||||
flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number,
|
||||
@ -18,6 +19,8 @@ export function anchorDef(
|
||||
if (matchedQueries) {
|
||||
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
||||
}
|
||||
// skip the call to sliceErrorStack itself + the call to this function.
|
||||
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
|
||||
return {
|
||||
type: NodeType.Element,
|
||||
// will bet set by the view definition
|
||||
@ -38,7 +41,7 @@ export function anchorDef(
|
||||
attrs: undefined,
|
||||
outputs: [], template,
|
||||
// will bet set by the view definition
|
||||
providerIndices: undefined,
|
||||
providerIndices: undefined, source
|
||||
},
|
||||
provider: undefined,
|
||||
text: undefined,
|
||||
@ -54,6 +57,8 @@ export function elementDef(
|
||||
([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] |
|
||||
[BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[],
|
||||
outputs?: (string | [string, string])[]): NodeDef {
|
||||
// skip the call to sliceErrorStack itself + the call to this function.
|
||||
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
|
||||
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
|
||||
if (matchedQueries) {
|
||||
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
||||
@ -112,7 +117,7 @@ export function elementDef(
|
||||
outputs: outputDefs,
|
||||
template: undefined,
|
||||
// will bet set by the view definition
|
||||
providerIndices: undefined,
|
||||
providerIndices: undefined, source
|
||||
},
|
||||
provider: undefined,
|
||||
text: undefined,
|
||||
@ -127,8 +132,10 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El
|
||||
const elDef = def.element;
|
||||
let el: any;
|
||||
if (view.renderer) {
|
||||
el = elDef.name ? view.renderer.createElement(parentNode, elDef.name) :
|
||||
view.renderer.createTemplateAnchor(parentNode);
|
||||
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);
|
||||
} else {
|
||||
el = elDef.name ? document.createElement(elDef.name) : document.createComment('');
|
||||
if (parentNode) {
|
||||
@ -183,18 +190,22 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El
|
||||
}
|
||||
|
||||
function renderEventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
||||
return (event: any) => { return view.def.handleEvent(view, index, eventName, event); };
|
||||
return entryAction(EntryAction.HandleEvent, (event: any) => {
|
||||
setCurrentNode(view, index);
|
||||
return view.def.handleEvent(view, index, eventName, event);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function directDomEventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
||||
return (event: any) => {
|
||||
return entryAction(EntryAction.HandleEvent, (event: any) => {
|
||||
setCurrentNode(view, index);
|
||||
const result = view.def.handleEvent(view, index, eventName, event);
|
||||
if (result === false) {
|
||||
event.preventDefault();
|
||||
}
|
||||
return result;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function checkAndUpdateElementInline(
|
||||
@ -314,7 +325,7 @@ function setElementProperty(
|
||||
let renderValue = securityContext ? view.services.sanitize(securityContext, value) : value;
|
||||
if (view.renderer) {
|
||||
view.renderer.setElementProperty(renderNode, name, renderValue);
|
||||
if (view.def.flags & ViewFlags.LogBindingUpdate) {
|
||||
if (isDevMode() && (view.def.flags & ViewFlags.DirectDom) === 0) {
|
||||
setBindingDebugInfo(view.renderer, renderNode, name, renderValue);
|
||||
}
|
||||
} else {
|
||||
|
43
modules/@angular/core/src/view/errors.ts
Normal file
43
modules/@angular/core/src/view/errors.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {BaseError, WrappedError} from '../facade/errors';
|
||||
|
||||
import {DebugContext} from './types';
|
||||
|
||||
export function expressionChangedAfterItHasBeenCheckedError(
|
||||
context: DebugContext, oldValue: any, currValue: any, isFirstCheck: boolean): ViewError {
|
||||
let msg =
|
||||
`Expression has changed after it was checked. Previous value: '${oldValue}'. Current value: '${currValue}'.`;
|
||||
if (isFirstCheck) {
|
||||
msg +=
|
||||
` It seems like the view has been created after its parent and its children have been dirty checked.` +
|
||||
` Has it been created in a change detection hook ?`;
|
||||
}
|
||||
return viewError(msg, context);
|
||||
}
|
||||
|
||||
export function viewWrappedError(originalError: any, context: DebugContext): WrappedError&
|
||||
ViewError {
|
||||
const err = viewError(originalError.message, context) as WrappedError & ViewError;
|
||||
err.originalError = originalError;
|
||||
return err;
|
||||
}
|
||||
|
||||
export interface ViewError { context: DebugContext; }
|
||||
|
||||
export function viewError(msg: string, context: DebugContext): ViewError {
|
||||
const err = new Error(msg) as any;
|
||||
err.context = context;
|
||||
err.stack = context.source;
|
||||
return err;
|
||||
}
|
||||
|
||||
export function isViewError(err: any): boolean {
|
||||
return err.context;
|
||||
}
|
@ -11,7 +11,8 @@ export {providerDef} from './provider';
|
||||
export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression';
|
||||
export {queryDef} from './query';
|
||||
export {textDef} from './text';
|
||||
export {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView, viewDef} from './view';
|
||||
export {setCurrentNode} from './util';
|
||||
export {checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createEmbeddedView, createRootView, destroyView, viewDef} from './view';
|
||||
export {attachEmbeddedView, detachEmbeddedView, rootRenderNodes} from './view_attach';
|
||||
|
||||
export * from './types';
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {SimpleChange, SimpleChanges} from '../change_detection/change_detection';
|
||||
import {Injector} from '../di';
|
||||
import {stringify} from '../facade/lang';
|
||||
@ -13,10 +14,10 @@ import {ElementRef} from '../linker/element_ref';
|
||||
import {TemplateRef} from '../linker/template_ref';
|
||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||
import {Renderer} from '../render/api';
|
||||
import {queryDef} from './query';
|
||||
|
||||
import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderOutputDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, ViewDefinition, ViewFlags, asElementData, asProviderData} from './types';
|
||||
import {checkAndUpdateBinding, checkAndUpdateBindingWithChange, setBindingDebugInfo} from './util';
|
||||
import {queryDef} from './query';
|
||||
import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderOutputDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, ViewDefinition, ViewFlags, asElementData, asProviderData} from './types';
|
||||
import {checkAndUpdateBinding, checkAndUpdateBindingWithChange, entryAction, setBindingDebugInfo, setCurrentNode} from './util';
|
||||
|
||||
const _tokenKeyCache = new Map<any, string>();
|
||||
|
||||
@ -82,7 +83,12 @@ export function providerDef(
|
||||
matchedQueries: matchedQueryDefs, childCount, bindings,
|
||||
disposableCount: outputDefs.length,
|
||||
element: undefined,
|
||||
provider: {tokenKey: tokenKey(ctor), ctor, deps: depDefs, outputs: outputDefs, component},
|
||||
provider: {
|
||||
tokenKey: tokenKey(ctor),
|
||||
token: ctor, ctor,
|
||||
deps: depDefs,
|
||||
outputs: outputDefs, component
|
||||
},
|
||||
text: undefined,
|
||||
pureExpression: undefined,
|
||||
query: undefined
|
||||
@ -106,13 +112,20 @@ export function createProvider(
|
||||
for (let i = 0; i < providerDef.outputs.length; i++) {
|
||||
const output = providerDef.outputs[i];
|
||||
const subscription = provider[output.propName].subscribe(
|
||||
view.def.handleEvent.bind(null, view, def.parent, output.eventName));
|
||||
eventHandlerClosure(view, def.parent, output.eventName));
|
||||
view.disposables[def.disposableIndex + i] = subscription.unsubscribe.bind(subscription);
|
||||
}
|
||||
}
|
||||
return {instance: provider, componentView: componentView};
|
||||
}
|
||||
|
||||
function eventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
||||
return entryAction(EntryAction.HandleEvent, (event: any) => {
|
||||
setCurrentNode(view, index);
|
||||
view.def.handleEvent(view, index, eventName, event);
|
||||
});
|
||||
}
|
||||
|
||||
export function checkAndUpdateProviderInline(
|
||||
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
|
||||
v7: any, v8: any, v9: any) {
|
||||
@ -239,6 +252,18 @@ export function resolveDep(
|
||||
return Injector.NULL.get(depDef.token, notFoundValue);
|
||||
}
|
||||
|
||||
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, this.elIndex, {flags: DepFlags.None, token, tokenKey: tokenKey(token)});
|
||||
}
|
||||
}
|
||||
|
||||
function checkAndUpdateProp(
|
||||
view: ViewData, provider: any, def: NodeDef, bindingIdx: number, value: any,
|
||||
changes: SimpleChanges): SimpleChanges {
|
||||
@ -258,7 +283,7 @@ function checkAndUpdateProp(
|
||||
// so Closure Compiler will have renamed the property correctly already.
|
||||
provider[propName] = value;
|
||||
|
||||
if (view.def.flags & ViewFlags.LogBindingUpdate) {
|
||||
if (isDevMode() && (view.def.flags & ViewFlags.DirectDom) === 0) {
|
||||
setBindingDebugInfo(
|
||||
view.renderer, asElementData(view, def.parent).renderElement, binding.nonMinifiedName,
|
||||
value);
|
||||
@ -282,6 +307,7 @@ export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: Node
|
||||
const nodeIndex = nodeDef.index;
|
||||
if (nodeDef.flags & lifecycles) {
|
||||
// a leaf
|
||||
setCurrentNode(view, nodeIndex);
|
||||
callProviderLifecycles(asProviderData(view, nodeIndex).instance, nodeDef.flags & lifecycles);
|
||||
} else if ((nodeDef.childFlags & lifecycles) === 0) {
|
||||
// a parent with leafs
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {ElementRef} from '../linker/element_ref';
|
||||
import {ExpressionChangedAfterItHasBeenCheckedError} from '../linker/errors';
|
||||
import {QueryList} from '../linker/query_list';
|
||||
import {TemplateRef} from '../linker/template_ref';
|
||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||
@ -110,24 +109,9 @@ function calcQueryValues(
|
||||
const len = view.def.nodes.length;
|
||||
for (let i = startIndex; i <= endIndex; i++) {
|
||||
const nodeDef = view.def.nodes[i];
|
||||
const queryValueType = <QueryValueType>nodeDef.matchedQueries[queryId];
|
||||
if (queryValueType != null) {
|
||||
const value = getQueryValue(view, nodeDef, queryId);
|
||||
if (value != null) {
|
||||
// a match
|
||||
let value: any;
|
||||
switch (queryValueType) {
|
||||
case QueryValueType.ElementRef:
|
||||
value = new ElementRef(asElementData(view, i).renderElement);
|
||||
break;
|
||||
case QueryValueType.TemplateRef:
|
||||
value = view.services.createTemplateRef(view, nodeDef);
|
||||
break;
|
||||
case QueryValueType.ViewContainerRef:
|
||||
value = view.services.createViewContainerRef(asElementData(view, i));
|
||||
break;
|
||||
case QueryValueType.Provider:
|
||||
value = asProviderData(view, i).instance;
|
||||
break;
|
||||
}
|
||||
values.push(value);
|
||||
}
|
||||
if (nodeDef.flags & NodeFlags.HasEmbeddedViews &&
|
||||
@ -158,3 +142,29 @@ function calcQueryValues(
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
export function getQueryValue(view: ViewData, nodeDef: NodeDef, queryId: string): any {
|
||||
const queryValueType = <QueryValueType>nodeDef.matchedQueries[queryId];
|
||||
if (queryValueType != null) {
|
||||
// a match
|
||||
let value: any;
|
||||
switch (queryValueType) {
|
||||
case QueryValueType.RenderElement:
|
||||
value = asElementData(view, nodeDef.index).renderElement;
|
||||
break;
|
||||
case QueryValueType.ElementRef:
|
||||
value = new ElementRef(asElementData(view, nodeDef.index).renderElement);
|
||||
break;
|
||||
case QueryValueType.TemplateRef:
|
||||
value = view.services.createTemplateRef(view, nodeDef);
|
||||
break;
|
||||
case QueryValueType.ViewContainerRef:
|
||||
value = view.services.createViewContainerRef(asElementData(view, nodeDef.index));
|
||||
break;
|
||||
case QueryValueType.Provider:
|
||||
value = asProviderData(view, nodeDef.index).instance;
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
@ -16,7 +16,10 @@ import {EmbeddedViewRef, ViewRef} from '../linker/view_ref';
|
||||
import {RenderComponentType, Renderer, RootRenderer} from '../render/api';
|
||||
import {Sanitizer, SecurityContext} from '../security';
|
||||
|
||||
import {ElementData, NodeData, NodeDef, Services, ViewData, ViewDefinition, asElementData} from './types';
|
||||
import {createInjector} from './provider';
|
||||
import {getQueryValue} from './query';
|
||||
import {DebugContext, ElementData, NodeData, NodeDef, NodeType, Services, ViewData, ViewDefinition, asElementData} from './types';
|
||||
import {isComponentView, renderNode} from './util';
|
||||
import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, destroyView} from './view';
|
||||
import {attachEmbeddedView, detachEmbeddedView, rootRenderNodes} from './view_attach';
|
||||
|
||||
@ -30,15 +33,15 @@ export class DefaultServices implements Services {
|
||||
sanitize(context: SecurityContext, value: string): string {
|
||||
return this._sanitizer.sanitize(context, value);
|
||||
}
|
||||
// Note: This needs to be here to prevent a cycle in source files.
|
||||
createViewContainerRef(data: ElementData): ViewContainerRef {
|
||||
return new ViewContainerRef_(data);
|
||||
}
|
||||
|
||||
// Note: This needs to be here to prevent a cycle in source files.
|
||||
createTemplateRef(parentView: ViewData, def: NodeDef): TemplateRef<any> {
|
||||
return new TemplateRef_(parentView, def);
|
||||
}
|
||||
createDebugContext(view: ViewData, nodeIndex: number): DebugContext {
|
||||
return new DebugContext_(view, nodeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
class ViewContainerRef_ implements ViewContainerRef {
|
||||
@ -132,3 +135,91 @@ class TemplateRef_ implements TemplateRef<any> {
|
||||
return new ElementRef(asElementData(this._parentView, this._def.index).renderElement);
|
||||
}
|
||||
}
|
||||
|
||||
class DebugContext_ implements DebugContext {
|
||||
private nodeDef: NodeDef;
|
||||
private elDef: NodeDef;
|
||||
constructor(public view: ViewData, public nodeIndex: number) {
|
||||
this.nodeDef = view.def.nodes[nodeIndex];
|
||||
this.elDef = findElementDef(view, nodeIndex);
|
||||
}
|
||||
get injector(): Injector { return createInjector(this.view, this.elDef.index); }
|
||||
get component(): any { return this.view.component; }
|
||||
get providerTokens(): any[] {
|
||||
const tokens: any[] = [];
|
||||
if (this.elDef) {
|
||||
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
|
||||
const childDef = this.view.def.nodes[i];
|
||||
if (childDef.type === NodeType.Provider) {
|
||||
tokens.push(childDef.provider.token);
|
||||
} else {
|
||||
i += childDef.childCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
get references(): {[key: string]: any} {
|
||||
const references: {[key: string]: any} = {};
|
||||
if (this.elDef) {
|
||||
collectReferences(this.view, this.elDef, references);
|
||||
|
||||
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
|
||||
const childDef = this.view.def.nodes[i];
|
||||
if (childDef.type === NodeType.Provider) {
|
||||
collectReferences(this.view, childDef, references);
|
||||
} else {
|
||||
i += childDef.childCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
return references;
|
||||
}
|
||||
get context(): any { return this.view.context; }
|
||||
get source(): string {
|
||||
if (this.nodeDef.type === NodeType.Text) {
|
||||
return this.nodeDef.text.source;
|
||||
} else {
|
||||
return this.elDef.element.source;
|
||||
}
|
||||
}
|
||||
get componentRenderElement() {
|
||||
const elData = findHostElement(this.view);
|
||||
return elData ? elData.renderElement : undefined;
|
||||
}
|
||||
get renderNode(): any {
|
||||
let nodeDef = this.nodeDef.type === NodeType.Text ? this.nodeDef : this.elDef;
|
||||
return renderNode(this.view, nodeDef);
|
||||
}
|
||||
}
|
||||
|
||||
function findHostElement(view: ViewData): ElementData {
|
||||
while (view && !isComponentView(view)) {
|
||||
view = view.parent;
|
||||
}
|
||||
if (view.parent) {
|
||||
const hostData = asElementData(view.parent, view.parentIndex);
|
||||
return hostData;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function findElementDef(view: ViewData, nodeIndex: number): NodeDef {
|
||||
const viewDef = view.def;
|
||||
let nodeDef = viewDef.nodes[nodeIndex];
|
||||
while (nodeDef) {
|
||||
if (nodeDef.type === NodeType.Element) {
|
||||
return nodeDef;
|
||||
}
|
||||
nodeDef = nodeDef.parent != null ? viewDef.nodes[nodeDef.parent] : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function collectReferences(view: ViewData, nodeDef: NodeDef, references: {[key: string]: any}) {
|
||||
for (let queryId in nodeDef.matchedQueries) {
|
||||
if (queryId.startsWith('#')) {
|
||||
references[queryId.slice(1)] = getQueryValue(view, nodeDef, queryId);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,12 +6,15 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {looseIdentical} from '../facade/lang';
|
||||
|
||||
import {BindingDef, BindingType, NodeData, NodeDef, NodeFlags, NodeType, Services, TextData, ViewData, asElementData, asTextData} from './types';
|
||||
import {checkAndUpdateBinding} from './util';
|
||||
import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types';
|
||||
import {checkAndUpdateBinding, sliceErrorStack} from './util';
|
||||
|
||||
export function textDef(constants: string[]): NodeDef {
|
||||
// skip the call to sliceErrorStack itself + the call to this function.
|
||||
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
|
||||
const bindings: BindingDef[] = new Array(constants.length - 1);
|
||||
for (let i = 1; i < constants.length; i++) {
|
||||
bindings[i - 1] = {
|
||||
@ -39,7 +42,7 @@ export function textDef(constants: string[]): NodeDef {
|
||||
disposableCount: 0,
|
||||
element: undefined,
|
||||
provider: undefined,
|
||||
text: {prefix: constants[0]},
|
||||
text: {prefix: constants[0], source},
|
||||
pureExpression: undefined,
|
||||
query: undefined,
|
||||
};
|
||||
@ -50,7 +53,9 @@ 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) {
|
||||
renderNode = view.renderer.createText(parentNode, def.text.prefix);
|
||||
const debugContext =
|
||||
isDevMode() ? view.services.createDebugContext(view, def.index) : undefined;
|
||||
renderNode = view.renderer.createText(parentNode, def.text.prefix, debugContext);
|
||||
} else {
|
||||
renderNode = document.createTextNode(def.text.prefix);
|
||||
if (parentNode) {
|
||||
|
@ -10,7 +10,7 @@ import {PipeTransform} from '../change_detection/change_detection';
|
||||
import {QueryList} from '../linker/query_list';
|
||||
import {TemplateRef} from '../linker/template_ref';
|
||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||
import {RenderComponentType, Renderer, RootRenderer} from '../render/api';
|
||||
import {RenderComponentType, RenderDebugInfo, Renderer, RootRenderer} from '../render/api';
|
||||
import {Sanitizer, SecurityContext} from '../security';
|
||||
|
||||
// -------------------------------------
|
||||
@ -44,14 +44,9 @@ export interface ViewDefinition {
|
||||
nodeMatchedQueries: {[queryId: string]: boolean};
|
||||
}
|
||||
|
||||
export type ViewUpdateFn = (updater: NodeUpdater, view: ViewData) => void;
|
||||
export type ViewDefinitionFactory = () => ViewDefinition;
|
||||
|
||||
export interface NodeUpdater {
|
||||
checkInline(
|
||||
view: ViewData, nodeIndex: number, v0?: any, v1?: any, v2?: any, v3?: any, v4?: any, v5?: any,
|
||||
v6?: any, v7?: any, v8?: any, v9?: any): any;
|
||||
checkDynamic(view: ViewData, nodeIndex: number, values: any[]): any;
|
||||
}
|
||||
export type ViewUpdateFn = (view: ViewData) => void;
|
||||
|
||||
export type ViewHandleEventFn =
|
||||
(view: ViewData, nodeIndex: number, eventName: string, event: any) => boolean;
|
||||
@ -61,7 +56,6 @@ export type ViewHandleEventFn =
|
||||
*/
|
||||
export enum ViewFlags {
|
||||
None = 0,
|
||||
LogBindingUpdate = 1 << 0,
|
||||
DirectDom = 1 << 1
|
||||
}
|
||||
|
||||
@ -149,6 +143,7 @@ export enum BindingType {
|
||||
|
||||
export enum QueryValueType {
|
||||
ElementRef,
|
||||
RenderElement,
|
||||
TemplateRef,
|
||||
ViewContainerRef,
|
||||
Provider
|
||||
@ -166,6 +161,7 @@ export interface ElementDef {
|
||||
* to indices in parent ElementDefs.
|
||||
*/
|
||||
providerIndices: {[tokenKey: string]: number};
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface ElementOutputDef {
|
||||
@ -174,12 +170,13 @@ export interface ElementOutputDef {
|
||||
}
|
||||
|
||||
export interface ProviderDef {
|
||||
token: any;
|
||||
tokenKey: string;
|
||||
ctor: any;
|
||||
deps: DepDef[];
|
||||
outputs: ProviderOutputDef[];
|
||||
// closure to allow recursive components
|
||||
component: () => ViewDefinition;
|
||||
component: ViewDefinitionFactory;
|
||||
}
|
||||
|
||||
export interface DepDef {
|
||||
@ -201,7 +198,10 @@ export interface ProviderOutputDef {
|
||||
eventName: string;
|
||||
}
|
||||
|
||||
export interface TextDef { prefix: string; }
|
||||
export interface TextDef {
|
||||
prefix: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface PureExpressionDef {
|
||||
type: PureExpressionType;
|
||||
@ -361,4 +361,24 @@ export interface Services {
|
||||
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;
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
// Other
|
||||
// -------------------------------------
|
||||
export enum EntryAction {
|
||||
CheckAndUpdate,
|
||||
CheckNoChanges,
|
||||
Create,
|
||||
Destroy,
|
||||
HandleEvent
|
||||
}
|
||||
|
||||
export interface DebugContext extends RenderDebugInfo {
|
||||
view: ViewData;
|
||||
nodeIndex: number;
|
||||
componentRenderElement: any;
|
||||
renderNode: any;
|
||||
}
|
||||
|
@ -6,13 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {devModeEqual} from '../change_detection/change_detection';
|
||||
import {SimpleChange} from '../change_detection/change_detection_util';
|
||||
import {looseIdentical} from '../facade/lang';
|
||||
import {ExpressionChangedAfterItHasBeenCheckedError} from '../linker/errors';
|
||||
import {Renderer} from '../render/api';
|
||||
|
||||
import {ElementData, NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewDefinition, asElementData, asTextData} from './types';
|
||||
import {expressionChangedAfterItHasBeenCheckedError, isViewError, viewWrappedError} from './errors';
|
||||
import {ElementData, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewDefinition, ViewDefinitionFactory, asElementData, asTextData} from './types';
|
||||
|
||||
export function setBindingDebugInfo(
|
||||
renderer: Renderer, renderNode: any, propName: string, value: any) {
|
||||
@ -36,7 +37,8 @@ export function checkBindingNoChanges(
|
||||
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
||||
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
||||
if (view.firstChange || !devModeEqual(oldValue, value)) {
|
||||
throw new ExpressionChangedAfterItHasBeenCheckedError(oldValue, value, view.firstChange);
|
||||
throw expressionChangedAfterItHasBeenCheckedError(
|
||||
view.services.createDebugContext(view, def.index), oldValue, value, view.firstChange);
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,4 +78,95 @@ export function renderNode(view: ViewData, def: NodeDef): any {
|
||||
case NodeType.Text:
|
||||
return asTextData(view, def.index).renderText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isComponentView(view: ViewData): boolean {
|
||||
return view.component === view.context && !!view.parent;
|
||||
}
|
||||
|
||||
const VIEW_DEFINITION_CACHE = new WeakMap<any, ViewDefinition>();
|
||||
|
||||
export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition {
|
||||
let value: ViewDefinition = VIEW_DEFINITION_CACHE.get(factory);
|
||||
if (!value) {
|
||||
value = factory();
|
||||
VIEW_DEFINITION_CACHE.set(factory, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function sliceErrorStack(start: number, end: number): string {
|
||||
let err: any;
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
const stack = err.stack || '';
|
||||
const lines = stack.split('\n');
|
||||
if (lines[0].startsWith('Error')) {
|
||||
// Chrome always adds the message to the stack as well...
|
||||
start++;
|
||||
end++;
|
||||
}
|
||||
return lines.slice(start, end).join('\n');
|
||||
}
|
||||
|
||||
let _currentAction: EntryAction;
|
||||
let _currentView: ViewData;
|
||||
let _currentNodeIndex: number;
|
||||
|
||||
export function currentView() {
|
||||
return _currentView;
|
||||
}
|
||||
|
||||
export function currentNodeIndex() {
|
||||
return _currentNodeIndex;
|
||||
}
|
||||
|
||||
export function currentAction() {
|
||||
return _currentAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the node that is currently worked on.
|
||||
* It needs to be called whenever we call user code,
|
||||
* or code of the framework that might throw as a valid use case.
|
||||
*/
|
||||
export function setCurrentNode(view: ViewData, nodeIndex: number) {
|
||||
_currentView = view;
|
||||
_currentNodeIndex = nodeIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a try/catch handler around the given function to wrap all
|
||||
* errors that occur into new errors that contain the current debug info
|
||||
* set via setCurrentNode.
|
||||
*/
|
||||
export function entryAction<A, R>(action: EntryAction, fn: (arg: A) => R): (arg: A) => R {
|
||||
return <any>function(arg: any) {
|
||||
const oldAction = _currentAction;
|
||||
const oldView = _currentView;
|
||||
const oldNodeIndex = _currentNodeIndex;
|
||||
_currentAction = action;
|
||||
// Note: We can't call `isDevMode()` outside of this closure as
|
||||
// it might not have been initialized.
|
||||
const result = isDevMode() ? callWithTryCatch(fn, arg) : fn(arg);
|
||||
_currentAction = oldAction;
|
||||
_currentView = oldView;
|
||||
_currentNodeIndex = oldNodeIndex;
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function callWithTryCatch(fn: (a: any) => any, arg: any): any {
|
||||
try {
|
||||
return fn(arg);
|
||||
} catch (e) {
|
||||
if (isViewError(e) || !_currentView) {
|
||||
throw e;
|
||||
}
|
||||
const debugContext = _currentView.services.createDebugContext(_currentView, _currentNodeIndex);
|
||||
throw viewWrappedError(e, debugContext);
|
||||
}
|
||||
}
|
||||
|
@ -6,16 +6,17 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ExpressionChangedAfterItHasBeenCheckedError} from '../linker/errors';
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {RenderComponentType, Renderer} from '../render/api';
|
||||
|
||||
import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element';
|
||||
import {expressionChangedAfterItHasBeenCheckedError} from './errors';
|
||||
import {callLifecycleHooksChildrenFirst, checkAndUpdateProviderDynamic, checkAndUpdateProviderInline, createProvider} from './provider';
|
||||
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
|
||||
import {checkAndUpdateQuery, createQuery, queryDef} from './query';
|
||||
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
|
||||
import {ElementDef, NodeData, NodeDef, NodeFlags, NodeType, NodeUpdater, ProviderData, ProviderDef, Services, TextDef, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types';
|
||||
import {checkBindingNoChanges} from './util';
|
||||
import {ElementDef, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types';
|
||||
import {checkBindingNoChanges, currentAction, currentNodeIndex, currentView, entryAction, isComponentView, resolveViewDefinition, setCurrentNode} from './util';
|
||||
|
||||
const NOOP = (): any => undefined;
|
||||
|
||||
@ -31,7 +32,7 @@ export function viewDef(
|
||||
const reverseChildNodes: NodeDef[] = new Array(nodesWithoutIndices.length);
|
||||
let viewBindingCount = 0;
|
||||
let viewDisposableCount = 0;
|
||||
let viewFlags = 0;
|
||||
let viewNodeFlags = 0;
|
||||
let viewMatchedQueries: {[queryId: string]: boolean} = {};
|
||||
let currentParent: NodeDef = null;
|
||||
let lastRootNode: NodeDef = null;
|
||||
@ -56,14 +57,15 @@ export function viewDef(
|
||||
});
|
||||
if (node.element) {
|
||||
node.element = cloneAndModifyElement(node.element, {
|
||||
providerIndices: Object.create(currentParent ? currentParent.element.providerIndices : null)
|
||||
providerIndices:
|
||||
Object.create(currentParent ? currentParent.element.providerIndices : null),
|
||||
});
|
||||
}
|
||||
nodes[i] = node;
|
||||
reverseChildNodes[reverseChildIndex] = node;
|
||||
validateNode(currentParent, node);
|
||||
|
||||
viewFlags |= node.flags;
|
||||
viewNodeFlags |= node.flags;
|
||||
copyInto(node.matchedQueries, viewMatchedQueries);
|
||||
viewBindingCount += node.bindings.length;
|
||||
viewDisposableCount += node.disposableCount;
|
||||
@ -99,7 +101,7 @@ export function viewDef(
|
||||
}
|
||||
|
||||
return {
|
||||
nodeFlags: viewFlags,
|
||||
nodeFlags: viewNodeFlags,
|
||||
nodeMatchedQueries: viewMatchedQueries, flags,
|
||||
nodes: nodes, reverseChildNodes,
|
||||
update: update || NOOP,
|
||||
@ -222,13 +224,20 @@ export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context
|
||||
// to get the parent of the anchor and use it as parentIndex.
|
||||
const view = createView(
|
||||
parent.services, parent, anchorDef.index, anchorDef.parent, anchorDef.element.template);
|
||||
initView(view, null, parent.component, context);
|
||||
initView(view, parent.component, context);
|
||||
createViewNodes(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
export function createRootView(services: Services, def: ViewDefinition, context?: any): ViewData {
|
||||
const view = createView(services, null, null, null, def);
|
||||
initView(view, null, context, context);
|
||||
/**
|
||||
* 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, null, resolveViewDefinition(defFactory));
|
||||
initView(view, context, context);
|
||||
createViewNodes(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@ -256,14 +265,31 @@ function createView(
|
||||
return view;
|
||||
}
|
||||
|
||||
function initView(view: ViewData, renderHost: any, component: any, context: any) {
|
||||
function initView(view: ViewData, component: any, context: any) {
|
||||
view.component = component;
|
||||
view.context = context;
|
||||
}
|
||||
|
||||
const createViewNodes: (view: ViewData) => void =
|
||||
entryAction(EntryAction.CheckNoChanges, _createViewNodes);
|
||||
|
||||
function _createViewNodes(view: ViewData) {
|
||||
let renderHost: any;
|
||||
if (isComponentView(view)) {
|
||||
renderHost = asElementData(view.parent, view.parentIndex).renderElement;
|
||||
if (view.renderer) {
|
||||
renderHost = view.renderer.createViewRoot(renderHost);
|
||||
}
|
||||
}
|
||||
|
||||
const def = view.def;
|
||||
const nodes = view.nodes;
|
||||
for (let i = 0; i < def.nodes.length; i++) {
|
||||
const nodeDef = def.nodes[i];
|
||||
let nodeData: any;
|
||||
// As the current node is being created, we have to use
|
||||
// the parent node as the current node for error messages, ...
|
||||
setCurrentNode(view, nodeDef.parent);
|
||||
switch (nodeDef.type) {
|
||||
case NodeType.Element:
|
||||
nodeData = createElement(view, renderHost, nodeDef);
|
||||
@ -276,9 +302,13 @@ function initView(view: ViewData, renderHost: any, component: any, context: any)
|
||||
if (nodeDef.provider.component) {
|
||||
const hostElIndex = nodeDef.parent;
|
||||
componentView = createView(
|
||||
view.services, view, hostElIndex, hostElIndex, nodeDef.provider.component());
|
||||
view.services, view, hostElIndex, hostElIndex,
|
||||
resolveViewDefinition(nodeDef.provider.component));
|
||||
}
|
||||
const providerData = nodeData = createProvider(view, nodeDef, componentView);
|
||||
if (componentView) {
|
||||
initView(componentView, providerData.instance, providerData.instance);
|
||||
}
|
||||
nodeData = createProvider(view, nodeDef, componentView);
|
||||
break;
|
||||
case NodeType.PureExpression:
|
||||
nodeData = createPureExpression(view, nodeDef);
|
||||
@ -289,63 +319,25 @@ function initView(view: ViewData, renderHost: any, component: any, context: any)
|
||||
}
|
||||
nodes[i] = nodeData;
|
||||
}
|
||||
execComponentViewsAction(view, ViewAction.InitComponent);
|
||||
execComponentViewsAction(view, ViewAction.CreateViewNodes);
|
||||
}
|
||||
|
||||
export function checkNoChangesView(view: ViewData) {
|
||||
view.def.update(CheckNoChanges, view);
|
||||
export const checkNoChangesView: (view: ViewData) => void =
|
||||
entryAction(EntryAction.CheckNoChanges, _checkNoChangesView);
|
||||
|
||||
function _checkNoChangesView(view: ViewData) {
|
||||
view.def.update(view);
|
||||
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
|
||||
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckNoChanges);
|
||||
execComponentViewsAction(view, ViewAction.CheckNoChanges);
|
||||
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckNoChanges);
|
||||
}
|
||||
|
||||
const CheckNoChanges: NodeUpdater = {
|
||||
checkInline: (view: ViewData, index: number, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any,
|
||||
v6: any, v7: any, v8: any, v9: any): void => {
|
||||
const nodeDef = view.def.nodes[index];
|
||||
// Note: fallthrough is intended!
|
||||
switch (nodeDef.bindings.length) {
|
||||
case 10:
|
||||
checkBindingNoChanges(view, nodeDef, 9, v9);
|
||||
case 9:
|
||||
checkBindingNoChanges(view, nodeDef, 8, v8);
|
||||
case 8:
|
||||
checkBindingNoChanges(view, nodeDef, 7, v7);
|
||||
case 7:
|
||||
checkBindingNoChanges(view, nodeDef, 6, v6);
|
||||
case 6:
|
||||
checkBindingNoChanges(view, nodeDef, 5, v5);
|
||||
case 5:
|
||||
checkBindingNoChanges(view, nodeDef, 4, v4);
|
||||
case 4:
|
||||
checkBindingNoChanges(view, nodeDef, 3, v3);
|
||||
case 3:
|
||||
checkBindingNoChanges(view, nodeDef, 2, v2);
|
||||
case 2:
|
||||
checkBindingNoChanges(view, nodeDef, 1, v1);
|
||||
case 1:
|
||||
checkBindingNoChanges(view, nodeDef, 0, v0);
|
||||
}
|
||||
if (nodeDef.type === NodeType.PureExpression) {
|
||||
return asPureExpressionData(view, index).value;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
checkDynamic: (view: ViewData, index: number, values: any[]): void => {
|
||||
const nodeDef = view.def.nodes[index];
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
checkBindingNoChanges(view, nodeDef, i, values[i]);
|
||||
}
|
||||
if (nodeDef.type === NodeType.PureExpression) {
|
||||
return asPureExpressionData(view, index).value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
export const checkAndUpdateView: (view: ViewData) => void =
|
||||
entryAction(EntryAction.CheckAndUpdate, _checkAndUpdateView);
|
||||
|
||||
export function checkAndUpdateView(view: ViewData) {
|
||||
view.def.update(CheckAndUpdate, view);
|
||||
function _checkAndUpdateView(view: ViewData) {
|
||||
view.def.update(view);
|
||||
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
|
||||
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckAndUpdate);
|
||||
|
||||
@ -359,52 +351,121 @@ export function checkAndUpdateView(view: ViewData) {
|
||||
view.firstChange = false;
|
||||
}
|
||||
|
||||
const CheckAndUpdate: NodeUpdater = {
|
||||
checkInline: (view: ViewData, index: number, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any,
|
||||
v6: any, v7: any, v8: any, v9: any): void => {
|
||||
const nodeDef = view.def.nodes[index];
|
||||
switch (nodeDef.type) {
|
||||
case NodeType.Element:
|
||||
checkAndUpdateElementInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||
return undefined;
|
||||
case NodeType.Text:
|
||||
checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||
return undefined;
|
||||
case NodeType.Provider:
|
||||
checkAndUpdateProviderInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||
return undefined;
|
||||
case NodeType.PureExpression:
|
||||
checkAndUpdatePureExpressionInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||
return asPureExpressionData(view, index).value;
|
||||
}
|
||||
},
|
||||
checkDynamic: (view: ViewData, index: number, values: any[]): void => {
|
||||
const nodeDef = view.def.nodes[index];
|
||||
switch (nodeDef.type) {
|
||||
case NodeType.Element:
|
||||
checkAndUpdateElementDynamic(view, nodeDef, values);
|
||||
return undefined;
|
||||
case NodeType.Text:
|
||||
checkAndUpdateTextDynamic(view, nodeDef, values);
|
||||
return undefined;
|
||||
case NodeType.Provider:
|
||||
checkAndUpdateProviderDynamic(view, nodeDef, values);
|
||||
return undefined;
|
||||
case NodeType.PureExpression:
|
||||
checkAndUpdatePureExpressionDynamic(view, nodeDef, values);
|
||||
return asPureExpressionData(view, index).value;
|
||||
}
|
||||
export function checkNodeInline(
|
||||
v0?: any, v1?: any, v2?: any, v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any,
|
||||
v9?: any): any {
|
||||
const action = currentAction();
|
||||
const view = currentView();
|
||||
const nodeIndex = currentNodeIndex();
|
||||
const nodeDef = view.def.nodes[nodeIndex];
|
||||
switch (action) {
|
||||
case EntryAction.CheckNoChanges:
|
||||
checkNodeNoChangesInline(view, nodeIndex, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||
break;
|
||||
case EntryAction.CheckAndUpdate:
|
||||
switch (nodeDef.type) {
|
||||
case NodeType.Element:
|
||||
checkAndUpdateElementInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||
break;
|
||||
case NodeType.Text:
|
||||
checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||
break;
|
||||
case NodeType.Provider:
|
||||
checkAndUpdateProviderInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||
break;
|
||||
case NodeType.PureExpression:
|
||||
checkAndUpdatePureExpressionInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Illegal State: In action ${EntryAction[action]}`);
|
||||
}
|
||||
};
|
||||
return nodeDef.type === NodeType.PureExpression ? asPureExpressionData(view, nodeIndex).value :
|
||||
undefined;
|
||||
}
|
||||
|
||||
export function checkNodeDynamic(values: any[]): any {
|
||||
const action = currentAction();
|
||||
const view = currentView();
|
||||
const nodeIndex = currentNodeIndex();
|
||||
const nodeDef = view.def.nodes[nodeIndex];
|
||||
switch (action) {
|
||||
case EntryAction.CheckNoChanges:
|
||||
checkNodeNoChangesDynamic(view, nodeIndex, values);
|
||||
break;
|
||||
case EntryAction.CheckAndUpdate:
|
||||
switch (nodeDef.type) {
|
||||
case NodeType.Element:
|
||||
checkAndUpdateElementDynamic(view, nodeDef, values);
|
||||
break;
|
||||
case NodeType.Text:
|
||||
checkAndUpdateTextDynamic(view, nodeDef, values);
|
||||
break;
|
||||
case NodeType.Provider:
|
||||
checkAndUpdateProviderDynamic(view, nodeDef, values);
|
||||
break;
|
||||
case NodeType.PureExpression:
|
||||
checkAndUpdatePureExpressionDynamic(view, nodeDef, values);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Illegal State: In action ${EntryAction[action]}`);
|
||||
}
|
||||
return nodeDef.type === NodeType.PureExpression ? asPureExpressionData(view, nodeIndex).value :
|
||||
undefined;
|
||||
}
|
||||
|
||||
function checkNodeNoChangesInline(
|
||||
view: ViewData, nodeIndex: number, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any,
|
||||
v6: any, v7: any, v8: any, v9: any): void {
|
||||
const nodeDef = view.def.nodes[nodeIndex];
|
||||
// Note: fallthrough is intended!
|
||||
switch (nodeDef.bindings.length) {
|
||||
case 10:
|
||||
checkBindingNoChanges(view, nodeDef, 9, v9);
|
||||
case 9:
|
||||
checkBindingNoChanges(view, nodeDef, 8, v8);
|
||||
case 8:
|
||||
checkBindingNoChanges(view, nodeDef, 7, v7);
|
||||
case 7:
|
||||
checkBindingNoChanges(view, nodeDef, 6, v6);
|
||||
case 6:
|
||||
checkBindingNoChanges(view, nodeDef, 5, v5);
|
||||
case 5:
|
||||
checkBindingNoChanges(view, nodeDef, 4, v4);
|
||||
case 4:
|
||||
checkBindingNoChanges(view, nodeDef, 3, v3);
|
||||
case 3:
|
||||
checkBindingNoChanges(view, nodeDef, 2, v2);
|
||||
case 2:
|
||||
checkBindingNoChanges(view, nodeDef, 1, v1);
|
||||
case 1:
|
||||
checkBindingNoChanges(view, nodeDef, 0, v0);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function checkNodeNoChangesDynamic(view: ViewData, nodeIndex: number, values: any[]): void {
|
||||
const nodeDef = view.def.nodes[nodeIndex];
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
checkBindingNoChanges(view, nodeDef, i, values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) {
|
||||
const queryList = asQueryList(view, nodeDef.index);
|
||||
if (queryList.dirty) {
|
||||
throw new ExpressionChangedAfterItHasBeenCheckedError(false, true, view.firstChange);
|
||||
throw expressionChangedAfterItHasBeenCheckedError(
|
||||
view.services.createDebugContext(view, nodeDef.index),
|
||||
`Query ${nodeDef.query.id} not dirty`, `Query ${nodeDef.query.id} dirty`, view.firstChange);
|
||||
}
|
||||
}
|
||||
|
||||
export function destroyView(view: ViewData) {
|
||||
export const destroyView: (view: ViewData) => void = entryAction(EntryAction.Destroy, _destroyView);
|
||||
|
||||
function _destroyView(view: ViewData) {
|
||||
callLifecycleHooksChildrenFirst(view, NodeFlags.OnDestroy);
|
||||
if (view.disposables) {
|
||||
for (let i = 0; i < view.disposables.length; i++) {
|
||||
@ -416,7 +477,7 @@ export function destroyView(view: ViewData) {
|
||||
}
|
||||
|
||||
enum ViewAction {
|
||||
InitComponent,
|
||||
CreateViewNodes,
|
||||
CheckNoChanges,
|
||||
CheckAndUpdate,
|
||||
Destroy
|
||||
@ -432,16 +493,7 @@ function execComponentViewsAction(view: ViewData, action: ViewAction) {
|
||||
if (nodeDef.flags & NodeFlags.HasComponent) {
|
||||
// a leaf
|
||||
const providerData = asProviderData(view, i);
|
||||
if (action === ViewAction.InitComponent) {
|
||||
let renderHost = asElementData(view, nodeDef.parent).renderElement;
|
||||
if (view.renderer) {
|
||||
renderHost = view.renderer.createViewRoot(renderHost);
|
||||
}
|
||||
initView(
|
||||
providerData.componentView, renderHost, providerData.instance, providerData.instance);
|
||||
} else {
|
||||
callViewAction(providerData.componentView, action);
|
||||
}
|
||||
callViewAction(providerData.componentView, action);
|
||||
} else if ((nodeDef.childFlags & NodeFlags.HasComponent) === 0) {
|
||||
// a parent with leafs
|
||||
// no child is a component,
|
||||
@ -478,13 +530,16 @@ function execEmbeddedViewsAction(view: ViewData, action: ViewAction) {
|
||||
function callViewAction(view: ViewData, action: ViewAction) {
|
||||
switch (action) {
|
||||
case ViewAction.CheckNoChanges:
|
||||
checkNoChangesView(view);
|
||||
_checkNoChangesView(view);
|
||||
break;
|
||||
case ViewAction.CheckAndUpdate:
|
||||
checkAndUpdateView(view);
|
||||
_checkAndUpdateView(view);
|
||||
break;
|
||||
case ViewAction.Destroy:
|
||||
destroyView(view);
|
||||
_destroyView(view);
|
||||
break;
|
||||
case ViewAction.CreateViewNodes:
|
||||
_createViewNodes(view);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -502,6 +557,7 @@ function execQueriesAction(view: ViewData, queryFlags: NodeFlags, action: QueryA
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
const nodeDef = view.def.nodes[i];
|
||||
if (nodeDef.flags & queryFlags) {
|
||||
setCurrentNode(view, nodeDef.index);
|
||||
switch (action) {
|
||||
case QueryAction.CheckAndUpdate:
|
||||
checkAndUpdateQuery(view, nodeDef);
|
||||
|
Reference in New Issue
Block a user