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:
parent
c48dd76f5c
commit
52b21275f4
@ -6,10 +6,11 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {isDevMode} from '../application_ref';
|
||||||
import {SecurityContext} from '../security';
|
import {SecurityContext} from '../security';
|
||||||
|
|
||||||
import {BindingDef, BindingType, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, ViewData, ViewDefinition, ViewFlags, asElementData} from './types';
|
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, ViewData, ViewDefinition, ViewFlags, asElementData} from './types';
|
||||||
import {checkAndUpdateBinding, setBindingDebugInfo} from './util';
|
import {checkAndUpdateBinding, entryAction, setBindingDebugInfo, setCurrentNode, sliceErrorStack} from './util';
|
||||||
|
|
||||||
export function anchorDef(
|
export function anchorDef(
|
||||||
flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number,
|
flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number,
|
||||||
@ -18,6 +19,8 @@ export function anchorDef(
|
|||||||
if (matchedQueries) {
|
if (matchedQueries) {
|
||||||
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
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 {
|
return {
|
||||||
type: NodeType.Element,
|
type: NodeType.Element,
|
||||||
// will bet set by the view definition
|
// will bet set by the view definition
|
||||||
@ -38,7 +41,7 @@ export function anchorDef(
|
|||||||
attrs: undefined,
|
attrs: undefined,
|
||||||
outputs: [], template,
|
outputs: [], template,
|
||||||
// will bet set by the view definition
|
// will bet set by the view definition
|
||||||
providerIndices: undefined,
|
providerIndices: undefined, source
|
||||||
},
|
},
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
text: undefined,
|
text: undefined,
|
||||||
@ -54,6 +57,8 @@ export function elementDef(
|
|||||||
([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] |
|
([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] |
|
||||||
[BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[],
|
[BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[],
|
||||||
outputs?: (string | [string, string])[]): NodeDef {
|
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} = {};
|
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
|
||||||
if (matchedQueries) {
|
if (matchedQueries) {
|
||||||
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
||||||
@ -112,7 +117,7 @@ export function elementDef(
|
|||||||
outputs: outputDefs,
|
outputs: outputDefs,
|
||||||
template: undefined,
|
template: undefined,
|
||||||
// will bet set by the view definition
|
// will bet set by the view definition
|
||||||
providerIndices: undefined,
|
providerIndices: undefined, source
|
||||||
},
|
},
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
text: undefined,
|
text: undefined,
|
||||||
@ -127,8 +132,10 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El
|
|||||||
const elDef = def.element;
|
const elDef = def.element;
|
||||||
let el: any;
|
let el: any;
|
||||||
if (view.renderer) {
|
if (view.renderer) {
|
||||||
el = elDef.name ? view.renderer.createElement(parentNode, elDef.name) :
|
const debugContext =
|
||||||
view.renderer.createTemplateAnchor(parentNode);
|
isDevMode() ? view.services.createDebugContext(view, def.index) : undefined;
|
||||||
|
el = elDef.name ? view.renderer.createElement(parentNode, elDef.name, debugContext) :
|
||||||
|
view.renderer.createTemplateAnchor(parentNode, debugContext);
|
||||||
} else {
|
} else {
|
||||||
el = elDef.name ? document.createElement(elDef.name) : document.createComment('');
|
el = elDef.name ? document.createElement(elDef.name) : document.createComment('');
|
||||||
if (parentNode) {
|
if (parentNode) {
|
||||||
@ -183,18 +190,22 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderEventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
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) {
|
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);
|
const result = view.def.handleEvent(view, index, eventName, event);
|
||||||
if (result === false) {
|
if (result === false) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkAndUpdateElementInline(
|
export function checkAndUpdateElementInline(
|
||||||
@ -314,7 +325,7 @@ function setElementProperty(
|
|||||||
let renderValue = securityContext ? view.services.sanitize(securityContext, value) : value;
|
let renderValue = securityContext ? view.services.sanitize(securityContext, value) : value;
|
||||||
if (view.renderer) {
|
if (view.renderer) {
|
||||||
view.renderer.setElementProperty(renderNode, name, renderValue);
|
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);
|
setBindingDebugInfo(view.renderer, renderNode, name, renderValue);
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression';
|
||||||
export {queryDef} from './query';
|
export {queryDef} from './query';
|
||||||
export {textDef} from './text';
|
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 {attachEmbeddedView, detachEmbeddedView, rootRenderNodes} from './view_attach';
|
||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {isDevMode} from '../application_ref';
|
||||||
import {SimpleChange, SimpleChanges} from '../change_detection/change_detection';
|
import {SimpleChange, SimpleChanges} from '../change_detection/change_detection';
|
||||||
import {Injector} from '../di';
|
import {Injector} from '../di';
|
||||||
import {stringify} from '../facade/lang';
|
import {stringify} from '../facade/lang';
|
||||||
@ -13,10 +14,10 @@ import {ElementRef} from '../linker/element_ref';
|
|||||||
import {TemplateRef} from '../linker/template_ref';
|
import {TemplateRef} from '../linker/template_ref';
|
||||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||||
import {Renderer} from '../render/api';
|
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 {queryDef} from './query';
|
||||||
import {checkAndUpdateBinding, checkAndUpdateBindingWithChange, setBindingDebugInfo} from './util';
|
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>();
|
const _tokenKeyCache = new Map<any, string>();
|
||||||
|
|
||||||
@ -82,7 +83,12 @@ export function providerDef(
|
|||||||
matchedQueries: matchedQueryDefs, childCount, bindings,
|
matchedQueries: matchedQueryDefs, childCount, bindings,
|
||||||
disposableCount: outputDefs.length,
|
disposableCount: outputDefs.length,
|
||||||
element: undefined,
|
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,
|
text: undefined,
|
||||||
pureExpression: undefined,
|
pureExpression: undefined,
|
||||||
query: undefined
|
query: undefined
|
||||||
@ -106,13 +112,20 @@ export function createProvider(
|
|||||||
for (let i = 0; i < providerDef.outputs.length; i++) {
|
for (let i = 0; i < providerDef.outputs.length; i++) {
|
||||||
const output = providerDef.outputs[i];
|
const output = providerDef.outputs[i];
|
||||||
const subscription = provider[output.propName].subscribe(
|
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);
|
view.disposables[def.disposableIndex + i] = subscription.unsubscribe.bind(subscription);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {instance: provider, componentView: componentView};
|
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(
|
export function checkAndUpdateProviderInline(
|
||||||
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
|
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
|
||||||
v7: any, v8: any, v9: any) {
|
v7: any, v8: any, v9: any) {
|
||||||
@ -239,6 +252,18 @@ export function resolveDep(
|
|||||||
return Injector.NULL.get(depDef.token, notFoundValue);
|
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(
|
function checkAndUpdateProp(
|
||||||
view: ViewData, provider: any, def: NodeDef, bindingIdx: number, value: any,
|
view: ViewData, provider: any, def: NodeDef, bindingIdx: number, value: any,
|
||||||
changes: SimpleChanges): SimpleChanges {
|
changes: SimpleChanges): SimpleChanges {
|
||||||
@ -258,7 +283,7 @@ function checkAndUpdateProp(
|
|||||||
// so Closure Compiler will have renamed the property correctly already.
|
// so Closure Compiler will have renamed the property correctly already.
|
||||||
provider[propName] = value;
|
provider[propName] = value;
|
||||||
|
|
||||||
if (view.def.flags & ViewFlags.LogBindingUpdate) {
|
if (isDevMode() && (view.def.flags & ViewFlags.DirectDom) === 0) {
|
||||||
setBindingDebugInfo(
|
setBindingDebugInfo(
|
||||||
view.renderer, asElementData(view, def.parent).renderElement, binding.nonMinifiedName,
|
view.renderer, asElementData(view, def.parent).renderElement, binding.nonMinifiedName,
|
||||||
value);
|
value);
|
||||||
@ -282,6 +307,7 @@ export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: Node
|
|||||||
const nodeIndex = nodeDef.index;
|
const nodeIndex = nodeDef.index;
|
||||||
if (nodeDef.flags & lifecycles) {
|
if (nodeDef.flags & lifecycles) {
|
||||||
// a leaf
|
// a leaf
|
||||||
|
setCurrentNode(view, nodeIndex);
|
||||||
callProviderLifecycles(asProviderData(view, nodeIndex).instance, nodeDef.flags & lifecycles);
|
callProviderLifecycles(asProviderData(view, nodeIndex).instance, nodeDef.flags & lifecycles);
|
||||||
} else if ((nodeDef.childFlags & lifecycles) === 0) {
|
} else if ((nodeDef.childFlags & lifecycles) === 0) {
|
||||||
// a parent with leafs
|
// a parent with leafs
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {ElementRef} from '../linker/element_ref';
|
import {ElementRef} from '../linker/element_ref';
|
||||||
import {ExpressionChangedAfterItHasBeenCheckedError} from '../linker/errors';
|
|
||||||
import {QueryList} from '../linker/query_list';
|
import {QueryList} from '../linker/query_list';
|
||||||
import {TemplateRef} from '../linker/template_ref';
|
import {TemplateRef} from '../linker/template_ref';
|
||||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||||
@ -110,24 +109,9 @@ function calcQueryValues(
|
|||||||
const len = view.def.nodes.length;
|
const len = view.def.nodes.length;
|
||||||
for (let i = startIndex; i <= endIndex; i++) {
|
for (let i = startIndex; i <= endIndex; i++) {
|
||||||
const nodeDef = view.def.nodes[i];
|
const nodeDef = view.def.nodes[i];
|
||||||
const queryValueType = <QueryValueType>nodeDef.matchedQueries[queryId];
|
const value = getQueryValue(view, nodeDef, queryId);
|
||||||
if (queryValueType != null) {
|
if (value != null) {
|
||||||
// a match
|
// 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);
|
values.push(value);
|
||||||
}
|
}
|
||||||
if (nodeDef.flags & NodeFlags.HasEmbeddedViews &&
|
if (nodeDef.flags & NodeFlags.HasEmbeddedViews &&
|
||||||
@ -158,3 +142,29 @@ function calcQueryValues(
|
|||||||
}
|
}
|
||||||
return values;
|
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 {RenderComponentType, Renderer, RootRenderer} from '../render/api';
|
||||||
import {Sanitizer, SecurityContext} from '../security';
|
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 {checkAndUpdateView, checkNoChangesView, createEmbeddedView, destroyView} from './view';
|
||||||
import {attachEmbeddedView, detachEmbeddedView, rootRenderNodes} from './view_attach';
|
import {attachEmbeddedView, detachEmbeddedView, rootRenderNodes} from './view_attach';
|
||||||
|
|
||||||
@ -30,15 +33,15 @@ export class DefaultServices implements Services {
|
|||||||
sanitize(context: SecurityContext, value: string): string {
|
sanitize(context: SecurityContext, value: string): string {
|
||||||
return this._sanitizer.sanitize(context, value);
|
return this._sanitizer.sanitize(context, value);
|
||||||
}
|
}
|
||||||
// Note: This needs to be here to prevent a cycle in source files.
|
|
||||||
createViewContainerRef(data: ElementData): ViewContainerRef {
|
createViewContainerRef(data: ElementData): ViewContainerRef {
|
||||||
return new ViewContainerRef_(data);
|
return new ViewContainerRef_(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: This needs to be here to prevent a cycle in source files.
|
|
||||||
createTemplateRef(parentView: ViewData, def: NodeDef): TemplateRef<any> {
|
createTemplateRef(parentView: ViewData, def: NodeDef): TemplateRef<any> {
|
||||||
return new TemplateRef_(parentView, def);
|
return new TemplateRef_(parentView, def);
|
||||||
}
|
}
|
||||||
|
createDebugContext(view: ViewData, nodeIndex: number): DebugContext {
|
||||||
|
return new DebugContext_(view, nodeIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewContainerRef_ implements ViewContainerRef {
|
class ViewContainerRef_ implements ViewContainerRef {
|
||||||
@ -132,3 +135,91 @@ class TemplateRef_ implements TemplateRef<any> {
|
|||||||
return new ElementRef(asElementData(this._parentView, this._def.index).renderElement);
|
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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {isDevMode} from '../application_ref';
|
||||||
import {looseIdentical} from '../facade/lang';
|
import {looseIdentical} from '../facade/lang';
|
||||||
|
|
||||||
import {BindingDef, BindingType, NodeData, NodeDef, NodeFlags, NodeType, Services, TextData, ViewData, asElementData, asTextData} from './types';
|
import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types';
|
||||||
import {checkAndUpdateBinding} from './util';
|
import {checkAndUpdateBinding, sliceErrorStack} from './util';
|
||||||
|
|
||||||
export function textDef(constants: string[]): NodeDef {
|
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);
|
const bindings: BindingDef[] = new Array(constants.length - 1);
|
||||||
for (let i = 1; i < constants.length; i++) {
|
for (let i = 1; i < constants.length; i++) {
|
||||||
bindings[i - 1] = {
|
bindings[i - 1] = {
|
||||||
@ -39,7 +42,7 @@ export function textDef(constants: string[]): NodeDef {
|
|||||||
disposableCount: 0,
|
disposableCount: 0,
|
||||||
element: undefined,
|
element: undefined,
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
text: {prefix: constants[0]},
|
text: {prefix: constants[0], source},
|
||||||
pureExpression: undefined,
|
pureExpression: undefined,
|
||||||
query: 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;
|
def.parent != null ? asElementData(view, def.parent).renderElement : renderHost;
|
||||||
let renderNode: any;
|
let renderNode: any;
|
||||||
if (view.renderer) {
|
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 {
|
} else {
|
||||||
renderNode = document.createTextNode(def.text.prefix);
|
renderNode = document.createTextNode(def.text.prefix);
|
||||||
if (parentNode) {
|
if (parentNode) {
|
||||||
|
@ -10,7 +10,7 @@ import {PipeTransform} from '../change_detection/change_detection';
|
|||||||
import {QueryList} from '../linker/query_list';
|
import {QueryList} from '../linker/query_list';
|
||||||
import {TemplateRef} from '../linker/template_ref';
|
import {TemplateRef} from '../linker/template_ref';
|
||||||
import {ViewContainerRef} from '../linker/view_container_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';
|
import {Sanitizer, SecurityContext} from '../security';
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
@ -44,14 +44,9 @@ export interface ViewDefinition {
|
|||||||
nodeMatchedQueries: {[queryId: string]: boolean};
|
nodeMatchedQueries: {[queryId: string]: boolean};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ViewUpdateFn = (updater: NodeUpdater, view: ViewData) => void;
|
export type ViewDefinitionFactory = () => ViewDefinition;
|
||||||
|
|
||||||
export interface NodeUpdater {
|
export type ViewUpdateFn = (view: ViewData) => void;
|
||||||
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 ViewHandleEventFn =
|
export type ViewHandleEventFn =
|
||||||
(view: ViewData, nodeIndex: number, eventName: string, event: any) => boolean;
|
(view: ViewData, nodeIndex: number, eventName: string, event: any) => boolean;
|
||||||
@ -61,7 +56,6 @@ export type ViewHandleEventFn =
|
|||||||
*/
|
*/
|
||||||
export enum ViewFlags {
|
export enum ViewFlags {
|
||||||
None = 0,
|
None = 0,
|
||||||
LogBindingUpdate = 1 << 0,
|
|
||||||
DirectDom = 1 << 1
|
DirectDom = 1 << 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +143,7 @@ export enum BindingType {
|
|||||||
|
|
||||||
export enum QueryValueType {
|
export enum QueryValueType {
|
||||||
ElementRef,
|
ElementRef,
|
||||||
|
RenderElement,
|
||||||
TemplateRef,
|
TemplateRef,
|
||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
Provider
|
Provider
|
||||||
@ -166,6 +161,7 @@ export interface ElementDef {
|
|||||||
* to indices in parent ElementDefs.
|
* to indices in parent ElementDefs.
|
||||||
*/
|
*/
|
||||||
providerIndices: {[tokenKey: string]: number};
|
providerIndices: {[tokenKey: string]: number};
|
||||||
|
source: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ElementOutputDef {
|
export interface ElementOutputDef {
|
||||||
@ -174,12 +170,13 @@ export interface ElementOutputDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ProviderDef {
|
export interface ProviderDef {
|
||||||
|
token: any;
|
||||||
tokenKey: string;
|
tokenKey: string;
|
||||||
ctor: any;
|
ctor: any;
|
||||||
deps: DepDef[];
|
deps: DepDef[];
|
||||||
outputs: ProviderOutputDef[];
|
outputs: ProviderOutputDef[];
|
||||||
// closure to allow recursive components
|
// closure to allow recursive components
|
||||||
component: () => ViewDefinition;
|
component: ViewDefinitionFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DepDef {
|
export interface DepDef {
|
||||||
@ -201,7 +198,10 @@ export interface ProviderOutputDef {
|
|||||||
eventName: string;
|
eventName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TextDef { prefix: string; }
|
export interface TextDef {
|
||||||
|
prefix: string;
|
||||||
|
source: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PureExpressionDef {
|
export interface PureExpressionDef {
|
||||||
type: PureExpressionType;
|
type: PureExpressionType;
|
||||||
@ -361,4 +361,24 @@ export interface Services {
|
|||||||
createViewContainerRef(data: ElementData): ViewContainerRef;
|
createViewContainerRef(data: ElementData): ViewContainerRef;
|
||||||
// Note: This needs to be here to prevent a cycle in source files.
|
// Note: This needs to be here to prevent a cycle in source files.
|
||||||
createTemplateRef(parentView: ViewData, def: NodeDef): TemplateRef<any>;
|
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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {isDevMode} from '../application_ref';
|
||||||
import {devModeEqual} from '../change_detection/change_detection';
|
import {devModeEqual} from '../change_detection/change_detection';
|
||||||
import {SimpleChange} from '../change_detection/change_detection_util';
|
import {SimpleChange} from '../change_detection/change_detection_util';
|
||||||
import {looseIdentical} from '../facade/lang';
|
import {looseIdentical} from '../facade/lang';
|
||||||
import {ExpressionChangedAfterItHasBeenCheckedError} from '../linker/errors';
|
|
||||||
import {Renderer} from '../render/api';
|
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(
|
export function setBindingDebugInfo(
|
||||||
renderer: Renderer, renderNode: any, propName: string, value: any) {
|
renderer: Renderer, renderNode: any, propName: string, value: any) {
|
||||||
@ -36,7 +37,8 @@ export function checkBindingNoChanges(
|
|||||||
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
||||||
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
||||||
if (view.firstChange || !devModeEqual(oldValue, value)) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,3 +79,94 @@ export function renderNode(view: ViewData, def: NodeDef): any {
|
|||||||
return asTextData(view, def.index).renderText;
|
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
|
* 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 {RenderComponentType, Renderer} from '../render/api';
|
||||||
|
|
||||||
import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element';
|
import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element';
|
||||||
|
import {expressionChangedAfterItHasBeenCheckedError} from './errors';
|
||||||
import {callLifecycleHooksChildrenFirst, checkAndUpdateProviderDynamic, checkAndUpdateProviderInline, createProvider} from './provider';
|
import {callLifecycleHooksChildrenFirst, checkAndUpdateProviderDynamic, checkAndUpdateProviderInline, createProvider} from './provider';
|
||||||
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
|
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
|
||||||
import {checkAndUpdateQuery, createQuery, queryDef} from './query';
|
import {checkAndUpdateQuery, createQuery, queryDef} from './query';
|
||||||
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
|
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 {ElementDef, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types';
|
||||||
import {checkBindingNoChanges} from './util';
|
import {checkBindingNoChanges, currentAction, currentNodeIndex, currentView, entryAction, isComponentView, resolveViewDefinition, setCurrentNode} from './util';
|
||||||
|
|
||||||
const NOOP = (): any => undefined;
|
const NOOP = (): any => undefined;
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ export function viewDef(
|
|||||||
const reverseChildNodes: NodeDef[] = new Array(nodesWithoutIndices.length);
|
const reverseChildNodes: NodeDef[] = new Array(nodesWithoutIndices.length);
|
||||||
let viewBindingCount = 0;
|
let viewBindingCount = 0;
|
||||||
let viewDisposableCount = 0;
|
let viewDisposableCount = 0;
|
||||||
let viewFlags = 0;
|
let viewNodeFlags = 0;
|
||||||
let viewMatchedQueries: {[queryId: string]: boolean} = {};
|
let viewMatchedQueries: {[queryId: string]: boolean} = {};
|
||||||
let currentParent: NodeDef = null;
|
let currentParent: NodeDef = null;
|
||||||
let lastRootNode: NodeDef = null;
|
let lastRootNode: NodeDef = null;
|
||||||
@ -56,14 +57,15 @@ export function viewDef(
|
|||||||
});
|
});
|
||||||
if (node.element) {
|
if (node.element) {
|
||||||
node.element = cloneAndModifyElement(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;
|
nodes[i] = node;
|
||||||
reverseChildNodes[reverseChildIndex] = node;
|
reverseChildNodes[reverseChildIndex] = node;
|
||||||
validateNode(currentParent, node);
|
validateNode(currentParent, node);
|
||||||
|
|
||||||
viewFlags |= node.flags;
|
viewNodeFlags |= node.flags;
|
||||||
copyInto(node.matchedQueries, viewMatchedQueries);
|
copyInto(node.matchedQueries, viewMatchedQueries);
|
||||||
viewBindingCount += node.bindings.length;
|
viewBindingCount += node.bindings.length;
|
||||||
viewDisposableCount += node.disposableCount;
|
viewDisposableCount += node.disposableCount;
|
||||||
@ -99,7 +101,7 @@ export function viewDef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodeFlags: viewFlags,
|
nodeFlags: viewNodeFlags,
|
||||||
nodeMatchedQueries: viewMatchedQueries, flags,
|
nodeMatchedQueries: viewMatchedQueries, flags,
|
||||||
nodes: nodes, reverseChildNodes,
|
nodes: nodes, reverseChildNodes,
|
||||||
update: update || NOOP,
|
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.
|
// to get the parent of the anchor and use it as parentIndex.
|
||||||
const view = createView(
|
const view = createView(
|
||||||
parent.services, parent, anchorDef.index, anchorDef.parent, anchorDef.element.template);
|
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;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createRootView(services: Services, def: ViewDefinition, context?: any): ViewData {
|
/**
|
||||||
const view = createView(services, null, null, null, def);
|
* We take in a ViewDefinitionFactory, so that we can initialize the debug/prod mode first,
|
||||||
initView(view, null, context, context);
|
* 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;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,14 +265,31 @@ function createView(
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initView(view: ViewData, renderHost: any, component: any, context: any) {
|
function initView(view: ViewData, component: any, context: any) {
|
||||||
view.component = component;
|
view.component = component;
|
||||||
view.context = context;
|
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 def = view.def;
|
||||||
const nodes = view.nodes;
|
const nodes = view.nodes;
|
||||||
for (let i = 0; i < def.nodes.length; i++) {
|
for (let i = 0; i < def.nodes.length; i++) {
|
||||||
const nodeDef = def.nodes[i];
|
const nodeDef = def.nodes[i];
|
||||||
let nodeData: any;
|
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) {
|
switch (nodeDef.type) {
|
||||||
case NodeType.Element:
|
case NodeType.Element:
|
||||||
nodeData = createElement(view, renderHost, nodeDef);
|
nodeData = createElement(view, renderHost, nodeDef);
|
||||||
@ -276,9 +302,13 @@ function initView(view: ViewData, renderHost: any, component: any, context: any)
|
|||||||
if (nodeDef.provider.component) {
|
if (nodeDef.provider.component) {
|
||||||
const hostElIndex = nodeDef.parent;
|
const hostElIndex = nodeDef.parent;
|
||||||
componentView = createView(
|
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;
|
break;
|
||||||
case NodeType.PureExpression:
|
case NodeType.PureExpression:
|
||||||
nodeData = createPureExpression(view, nodeDef);
|
nodeData = createPureExpression(view, nodeDef);
|
||||||
@ -289,21 +319,108 @@ function initView(view: ViewData, renderHost: any, component: any, context: any)
|
|||||||
}
|
}
|
||||||
nodes[i] = nodeData;
|
nodes[i] = nodeData;
|
||||||
}
|
}
|
||||||
execComponentViewsAction(view, ViewAction.InitComponent);
|
execComponentViewsAction(view, ViewAction.CreateViewNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkNoChangesView(view: ViewData) {
|
export const checkNoChangesView: (view: ViewData) => void =
|
||||||
view.def.update(CheckNoChanges, view);
|
entryAction(EntryAction.CheckNoChanges, _checkNoChangesView);
|
||||||
|
|
||||||
|
function _checkNoChangesView(view: ViewData) {
|
||||||
|
view.def.update(view);
|
||||||
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
|
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
|
||||||
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckNoChanges);
|
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckNoChanges);
|
||||||
execComponentViewsAction(view, ViewAction.CheckNoChanges);
|
execComponentViewsAction(view, ViewAction.CheckNoChanges);
|
||||||
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckNoChanges);
|
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckNoChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CheckNoChanges: NodeUpdater = {
|
export const checkAndUpdateView: (view: ViewData) => void =
|
||||||
checkInline: (view: ViewData, index: number, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any,
|
entryAction(EntryAction.CheckAndUpdate, _checkAndUpdateView);
|
||||||
v6: any, v7: any, v8: any, v9: any): void => {
|
|
||||||
const nodeDef = view.def.nodes[index];
|
function _checkAndUpdateView(view: ViewData) {
|
||||||
|
view.def.update(view);
|
||||||
|
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
|
||||||
|
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckAndUpdate);
|
||||||
|
|
||||||
|
callLifecycleHooksChildrenFirst(
|
||||||
|
view, NodeFlags.AfterContentChecked | (view.firstChange ? NodeFlags.AfterContentInit : 0));
|
||||||
|
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
|
||||||
|
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckAndUpdate);
|
||||||
|
|
||||||
|
callLifecycleHooksChildrenFirst(
|
||||||
|
view, NodeFlags.AfterViewChecked | (view.firstChange ? NodeFlags.AfterViewInit : 0));
|
||||||
|
view.firstChange = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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!
|
// Note: fallthrough is intended!
|
||||||
switch (nodeDef.bindings.length) {
|
switch (nodeDef.bindings.length) {
|
||||||
case 10:
|
case 10:
|
||||||
@ -327,84 +444,28 @@ const CheckNoChanges: NodeUpdater = {
|
|||||||
case 1:
|
case 1:
|
||||||
checkBindingNoChanges(view, nodeDef, 0, v0);
|
checkBindingNoChanges(view, nodeDef, 0, v0);
|
||||||
}
|
}
|
||||||
if (nodeDef.type === NodeType.PureExpression) {
|
|
||||||
return asPureExpressionData(view, index).value;
|
|
||||||
}
|
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
}
|
||||||
checkDynamic: (view: ViewData, index: number, values: any[]): void => {
|
|
||||||
const nodeDef = view.def.nodes[index];
|
function checkNodeNoChangesDynamic(view: ViewData, nodeIndex: number, values: any[]): void {
|
||||||
|
const nodeDef = view.def.nodes[nodeIndex];
|
||||||
for (let i = 0; i < values.length; i++) {
|
for (let i = 0; i < values.length; i++) {
|
||||||
checkBindingNoChanges(view, nodeDef, i, values[i]);
|
checkBindingNoChanges(view, nodeDef, i, values[i]);
|
||||||
}
|
}
|
||||||
if (nodeDef.type === NodeType.PureExpression) {
|
|
||||||
return asPureExpressionData(view, index).value;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export function checkAndUpdateView(view: ViewData) {
|
|
||||||
view.def.update(CheckAndUpdate, view);
|
|
||||||
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
|
|
||||||
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckAndUpdate);
|
|
||||||
|
|
||||||
callLifecycleHooksChildrenFirst(
|
|
||||||
view, NodeFlags.AfterContentChecked | (view.firstChange ? NodeFlags.AfterContentInit : 0));
|
|
||||||
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
|
|
||||||
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckAndUpdate);
|
|
||||||
|
|
||||||
callLifecycleHooksChildrenFirst(
|
|
||||||
view, NodeFlags.AfterViewChecked | (view.firstChange ? NodeFlags.AfterViewInit : 0));
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) {
|
function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) {
|
||||||
const queryList = asQueryList(view, nodeDef.index);
|
const queryList = asQueryList(view, nodeDef.index);
|
||||||
if (queryList.dirty) {
|
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);
|
callLifecycleHooksChildrenFirst(view, NodeFlags.OnDestroy);
|
||||||
if (view.disposables) {
|
if (view.disposables) {
|
||||||
for (let i = 0; i < view.disposables.length; i++) {
|
for (let i = 0; i < view.disposables.length; i++) {
|
||||||
@ -416,7 +477,7 @@ export function destroyView(view: ViewData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ViewAction {
|
enum ViewAction {
|
||||||
InitComponent,
|
CreateViewNodes,
|
||||||
CheckNoChanges,
|
CheckNoChanges,
|
||||||
CheckAndUpdate,
|
CheckAndUpdate,
|
||||||
Destroy
|
Destroy
|
||||||
@ -432,16 +493,7 @@ function execComponentViewsAction(view: ViewData, action: ViewAction) {
|
|||||||
if (nodeDef.flags & NodeFlags.HasComponent) {
|
if (nodeDef.flags & NodeFlags.HasComponent) {
|
||||||
// a leaf
|
// a leaf
|
||||||
const providerData = asProviderData(view, i);
|
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) {
|
} else if ((nodeDef.childFlags & NodeFlags.HasComponent) === 0) {
|
||||||
// a parent with leafs
|
// a parent with leafs
|
||||||
// no child is a component,
|
// no child is a component,
|
||||||
@ -478,13 +530,16 @@ function execEmbeddedViewsAction(view: ViewData, action: ViewAction) {
|
|||||||
function callViewAction(view: ViewData, action: ViewAction) {
|
function callViewAction(view: ViewData, action: ViewAction) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case ViewAction.CheckNoChanges:
|
case ViewAction.CheckNoChanges:
|
||||||
checkNoChangesView(view);
|
_checkNoChangesView(view);
|
||||||
break;
|
break;
|
||||||
case ViewAction.CheckAndUpdate:
|
case ViewAction.CheckAndUpdate:
|
||||||
checkAndUpdateView(view);
|
_checkAndUpdateView(view);
|
||||||
break;
|
break;
|
||||||
case ViewAction.Destroy:
|
case ViewAction.Destroy:
|
||||||
destroyView(view);
|
_destroyView(view);
|
||||||
|
break;
|
||||||
|
case ViewAction.CreateViewNodes:
|
||||||
|
_createViewNodes(view);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -502,6 +557,7 @@ function execQueriesAction(view: ViewData, queryFlags: NodeFlags, action: QueryA
|
|||||||
for (let i = 0; i < nodeCount; i++) {
|
for (let i = 0; i < nodeCount; i++) {
|
||||||
const nodeDef = view.def.nodes[i];
|
const nodeDef = view.def.nodes[i];
|
||||||
if (nodeDef.flags & queryFlags) {
|
if (nodeDef.flags & queryFlags) {
|
||||||
|
setCurrentNode(view, nodeDef.index);
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case QueryAction.CheckAndUpdate:
|
case QueryAction.CheckAndUpdate:
|
||||||
checkAndUpdateQuery(view, nodeDef);
|
checkAndUpdateQuery(view, nodeDef);
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, getDebugNode} from '@angular/core';
|
||||||
import {DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
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 {inject} from '@angular/core/testing';
|
import {inject} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
|
||||||
@ -39,8 +39,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType);
|
return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
function createAndGetRootNodes(
|
||||||
const view = createRootView(services, viewDef);
|
viewDef: ViewDefinition, ctx?: any): {rootNodes: any[], view: ViewData} {
|
||||||
|
const view = createRootView(services, () => viewDef, ctx);
|
||||||
const rootNodes = rootRenderNodes(view);
|
const rootNodes = rootRenderNodes(view);
|
||||||
return {rootNodes, view};
|
return {rootNodes, view};
|
||||||
}
|
}
|
||||||
@ -66,6 +67,15 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
])).rootNodes;
|
])).rootNodes;
|
||||||
expect(getDOM().childNodes(rootNodes[0]).length).toBe(1);
|
expect(getDOM().childNodes(rootNodes[0]).length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!config.directDom) {
|
||||||
|
it('should add debug information to the renderer', () => {
|
||||||
|
const someContext = new Object();
|
||||||
|
const {view, rootNodes} =
|
||||||
|
createAndGetRootNodes(compViewDef([anchorDef(NodeFlags.None, null, 0)]), someContext);
|
||||||
|
expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asElementData(view, 0).renderElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
||||||
import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, checkAndUpdateView, checkNoChangesView, createRootView, destroyView, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
import {BindingType, DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, destroyView, elementDef, providerDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
import {inject} from '@angular/core/testing';
|
import {inject} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
||||||
const view = createRootView(services, viewDef);
|
const view = createRootView(services, () => viewDef);
|
||||||
const rootNodes = rootRenderNodes(view);
|
const rootNodes = rootRenderNodes(view);
|
||||||
return {rootNodes, view};
|
return {rootNodes, view};
|
||||||
}
|
}
|
||||||
@ -75,8 +75,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
a: any;
|
a: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const update = jasmine.createSpy('updater').and.callFake(
|
const update = jasmine.createSpy('updater').and.callFake((view: ViewData) => {
|
||||||
(updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, value));
|
setCurrentNode(view, 0);
|
||||||
|
checkNodeInline(value);
|
||||||
|
});
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(
|
const {view, rootNodes} = createAndGetRootNodes(
|
||||||
compViewDef([
|
compViewDef([
|
||||||
@ -91,14 +93,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
|
|
||||||
checkAndUpdateView(view);
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
expect(update).toHaveBeenCalled();
|
expect(update).toHaveBeenCalledWith(compView);
|
||||||
expect(update.calls.mostRecent().args[1]).toBe(compView);
|
|
||||||
|
|
||||||
update.calls.reset();
|
update.calls.reset();
|
||||||
checkNoChangesView(view);
|
checkNoChangesView(view);
|
||||||
|
|
||||||
expect(update).toHaveBeenCalled();
|
expect(update).toHaveBeenCalledWith(compView);
|
||||||
expect(update.calls.mostRecent().args[1]).toBe(compView);
|
|
||||||
|
|
||||||
value = 'v2';
|
value = 'v2';
|
||||||
expect(() => checkNoChangesView(view))
|
expect(() => checkNoChangesView(view))
|
||||||
|
@ -6,12 +6,12 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, getDebugNode} from '@angular/core';
|
||||||
import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, destroyView, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
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 {inject} from '@angular/core/testing';
|
import {inject} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
|
||||||
import {isBrowser, setupAndCheckRenderer} from './helper';
|
import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, isBrowser, setupAndCheckRenderer} from './helper';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
if (isBrowser()) {
|
if (isBrowser()) {
|
||||||
@ -39,8 +39,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType);
|
return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
function createAndGetRootNodes(
|
||||||
const view = createRootView(services, viewDef);
|
viewDef: ViewDefinition, context?: any): {rootNodes: any[], view: ViewData} {
|
||||||
|
const view = createRootView(services, () => viewDef, context);
|
||||||
const rootNodes = rootRenderNodes(view);
|
const rootNodes = rootRenderNodes(view);
|
||||||
return {rootNodes, view};
|
return {rootNodes, view};
|
||||||
}
|
}
|
||||||
@ -79,38 +80,20 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
expect(rootNodes.length).toBe(1);
|
expect(rootNodes.length).toBe(1);
|
||||||
expect(getDOM().getAttribute(rootNodes[0], 'title')).toBe('a');
|
expect(getDOM().getAttribute(rootNodes[0], 'title')).toBe('a');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!config.directDom) {
|
||||||
|
it('should add debug information to the renderer', () => {
|
||||||
|
const someContext = new Object();
|
||||||
|
const {view, rootNodes} = createAndGetRootNodes(
|
||||||
|
compViewDef([elementDef(NodeFlags.None, null, 0, 'div')]), someContext);
|
||||||
|
expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asElementData(view, 0).renderElement);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
it('should checkNoChanges', () => {
|
|
||||||
let attrValue = 'v1';
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
|
||||||
[
|
|
||||||
elementDef(
|
|
||||||
NodeFlags.None, null, 0, 'div', null,
|
|
||||||
[[BindingType.ElementAttribute, 'a1', SecurityContext.NONE]]),
|
|
||||||
],
|
|
||||||
(updater, view) => updater.checkInline(view, 0, attrValue)));
|
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
|
||||||
checkNoChangesView(view);
|
|
||||||
|
|
||||||
attrValue = 'v2';
|
|
||||||
expect(() => checkNoChangesView(view))
|
|
||||||
.toThrowError(
|
|
||||||
`Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('change properties', () => {
|
describe('change properties', () => {
|
||||||
[{
|
INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => {
|
||||||
name: 'inline',
|
it(`should update ${InlineDynamic[inlineDynamic]}`, () => {
|
||||||
update: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'v1', 'v2')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dynamic',
|
|
||||||
update: (updater: NodeUpdater, view: ViewData) =>
|
|
||||||
updater.checkDynamic(view, 0, ['v1', 'v2'])
|
|
||||||
}].forEach((config) => {
|
|
||||||
it(`should update ${config.name}`, () => {
|
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
@ -121,28 +104,27 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
[BindingType.ElementProperty, 'value', SecurityContext.NONE]
|
[BindingType.ElementProperty, 'value', SecurityContext.NONE]
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
config.update));
|
(view) => {
|
||||||
|
setCurrentNode(view, 0);
|
||||||
|
checkNodeInlineOrDynamic(inlineDynamic, ['v1', 'v2']);
|
||||||
|
}));
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
const el = rootNodes[0];
|
const el = rootNodes[0];
|
||||||
expect(getDOM().getProperty(el, 'title')).toBe('v1');
|
expect(getDOM().getProperty(el, 'title')).toBe('v1');
|
||||||
expect(getDOM().getProperty(el, 'value')).toBe('v2');
|
expect(getDOM().getProperty(el, 'value')).toBe('v2');
|
||||||
|
|
||||||
|
if (!config.directDom) {
|
||||||
|
expect(getDOM().getAttribute(el, 'ng-reflect-title')).toBe('v1');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('change attributes', () => {
|
describe('change attributes', () => {
|
||||||
[{
|
INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => {
|
||||||
name: 'inline',
|
it(`should update ${InlineDynamic[inlineDynamic]}`, () => {
|
||||||
update: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'v1', 'v2')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dynamic',
|
|
||||||
update: (updater: NodeUpdater, view: ViewData) =>
|
|
||||||
updater.checkDynamic(view, 0, ['v1', 'v2'])
|
|
||||||
}].forEach((config) => {
|
|
||||||
it(`should update ${config.name}`, () => {
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(
|
elementDef(
|
||||||
@ -152,7 +134,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
[BindingType.ElementAttribute, 'a2', SecurityContext.NONE]
|
[BindingType.ElementAttribute, 'a2', SecurityContext.NONE]
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
config.update));
|
(view) => {
|
||||||
|
setCurrentNode(view, 0);
|
||||||
|
checkNodeInlineOrDynamic(inlineDynamic, ['v1', 'v2']);
|
||||||
|
}));
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
@ -164,23 +149,18 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('change classes', () => {
|
describe('change classes', () => {
|
||||||
[{
|
INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => {
|
||||||
name: 'inline',
|
it(`should update ${InlineDynamic[inlineDynamic]}`, () => {
|
||||||
updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, true, true)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dynamic',
|
|
||||||
updater: (updater: NodeUpdater, view: ViewData) =>
|
|
||||||
updater.checkDynamic(view, 0, [true, true])
|
|
||||||
}].forEach((config) => {
|
|
||||||
it(`should update ${config.name}`, () => {
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(
|
elementDef(
|
||||||
NodeFlags.None, null, 0, 'div', null,
|
NodeFlags.None, null, 0, 'div', null,
|
||||||
[[BindingType.ElementClass, 'c1'], [BindingType.ElementClass, 'c2']]),
|
[[BindingType.ElementClass, 'c1'], [BindingType.ElementClass, 'c2']]),
|
||||||
],
|
],
|
||||||
config.updater));
|
(view) => {
|
||||||
|
setCurrentNode(view, 0);
|
||||||
|
checkNodeInlineOrDynamic(inlineDynamic, [true, true]);
|
||||||
|
}));
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
@ -192,16 +172,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('change styles', () => {
|
describe('change styles', () => {
|
||||||
[{
|
INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => {
|
||||||
name: 'inline',
|
it(`should update ${InlineDynamic[inlineDynamic]}`, () => {
|
||||||
update: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 10, 'red')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dynamic',
|
|
||||||
update: (updater: NodeUpdater, view: ViewData) =>
|
|
||||||
updater.checkDynamic(view, 0, [10, 'red'])
|
|
||||||
}].forEach((config) => {
|
|
||||||
it(`should update ${config.name}`, () => {
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(
|
elementDef(
|
||||||
@ -211,7 +183,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
[BindingType.ElementStyle, 'color', null]
|
[BindingType.ElementStyle, 'color', null]
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
config.update));
|
(view) => {
|
||||||
|
setCurrentNode(view, 0);
|
||||||
|
checkNodeInlineOrDynamic(inlineDynamic, [10, 'red']);
|
||||||
|
}));
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
@ -346,6 +321,24 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
expect(preventDefaultSpy).toHaveBeenCalled();
|
expect(preventDefaultSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should report debug info on event errors', () => {
|
||||||
|
const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough();
|
||||||
|
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
||||||
|
[elementDef(NodeFlags.None, null, 0, 'button', null, null, ['click'])], null,
|
||||||
|
() => { throw new Error('Test'); }));
|
||||||
|
|
||||||
|
let err: any;
|
||||||
|
try {
|
||||||
|
addListenerSpy.calls.mostRecent().args[1]('SomeEvent');
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
expect(err).toBeTruthy();
|
||||||
|
expect(err.message).toBe('Test');
|
||||||
|
const debugCtx = <DebugContext>err.context;
|
||||||
|
expect(debugCtx.view).toBe(view);
|
||||||
|
expect(debugCtx.nodeIndex).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
||||||
import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, attachEmbeddedView, checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView, detachEmbeddedView, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
import {BindingType, DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, attachEmbeddedView, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createEmbeddedView, createRootView, destroyView, detachEmbeddedView, elementDef, providerDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
import {inject} from '@angular/core/testing';
|
import {inject} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
|
|
||||||
function createAndGetRootNodes(
|
function createAndGetRootNodes(
|
||||||
viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} {
|
viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} {
|
||||||
const view = createRootView(services, viewDef, context);
|
const view = createRootView(services, () => viewDef, context);
|
||||||
const rootNodes = rootRenderNodes(view);
|
const rootNodes = rootRenderNodes(view);
|
||||||
return {rootNodes, view};
|
return {rootNodes, view};
|
||||||
}
|
}
|
||||||
@ -116,8 +116,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
|
|
||||||
it('should dirty check embedded views', () => {
|
it('should dirty check embedded views', () => {
|
||||||
let childValue = 'v1';
|
let childValue = 'v1';
|
||||||
const update = jasmine.createSpy('updater').and.callFake(
|
const update = jasmine.createSpy('updater').and.callFake((view: ViewData) => {
|
||||||
(updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, childValue));
|
setCurrentNode(view, 0);
|
||||||
|
checkNodeInline(childValue);
|
||||||
|
});
|
||||||
|
|
||||||
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
|
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, null, 1, 'div'),
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
@ -137,14 +139,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
|
|
||||||
checkAndUpdateView(parentView);
|
checkAndUpdateView(parentView);
|
||||||
|
|
||||||
expect(update).toHaveBeenCalled();
|
expect(update).toHaveBeenCalledWith(childView0);
|
||||||
expect(update.calls.mostRecent().args[1]).toBe(childView0);
|
|
||||||
|
|
||||||
update.calls.reset();
|
update.calls.reset();
|
||||||
checkNoChangesView(parentView);
|
checkNoChangesView(parentView);
|
||||||
|
|
||||||
expect(update).toHaveBeenCalled();
|
expect(update).toHaveBeenCalledWith(childView0);
|
||||||
expect(update.calls.mostRecent().args[1]).toBe(childView0);
|
|
||||||
|
|
||||||
childValue = 'v2';
|
childValue = 'v2';
|
||||||
expect(() => checkNoChangesView(parentView))
|
expect(() => checkNoChangesView(parentView))
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {RootRenderer} from '@angular/core';
|
import {RootRenderer} from '@angular/core';
|
||||||
import {NodeUpdater, ViewData} from '@angular/core/src/view/index';
|
import {checkNodeDynamic, checkNodeInline} from '@angular/core/src/view/index';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
|
||||||
@ -43,13 +43,11 @@ export enum InlineDynamic {
|
|||||||
|
|
||||||
export const INLINE_DYNAMIC_VALUES = [InlineDynamic.Inline, InlineDynamic.Dynamic];
|
export const INLINE_DYNAMIC_VALUES = [InlineDynamic.Inline, InlineDynamic.Dynamic];
|
||||||
|
|
||||||
export function callUpdater(
|
export function checkNodeInlineOrDynamic(inlineDynamic: InlineDynamic, values: any[]): any {
|
||||||
updater: NodeUpdater, inlineDynamic: InlineDynamic, view: ViewData, nodeIndex: number,
|
|
||||||
values: any[]): any {
|
|
||||||
switch (inlineDynamic) {
|
switch (inlineDynamic) {
|
||||||
case InlineDynamic.Inline:
|
case InlineDynamic.Inline:
|
||||||
return (<any>updater.checkInline)(view, nodeIndex, ...values);
|
return (<any>checkNodeInline)(...values);
|
||||||
case InlineDynamic.Dynamic:
|
case InlineDynamic.Dynamic:
|
||||||
return updater.checkDynamic(view, nodeIndex, values);
|
return checkNodeDynamic(values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,12 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, ElementRef, EventEmitter, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, ElementRef, EventEmitter, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core';
|
||||||
import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, destroyView, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
import {BindingType, DebugContext, DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, destroyView, elementDef, providerDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
import {inject} from '@angular/core/testing';
|
import {inject} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
|
||||||
import {isBrowser, setupAndCheckRenderer} from './helper';
|
import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, isBrowser, setupAndCheckRenderer} from './helper';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
if (isBrowser()) {
|
if (isBrowser()) {
|
||||||
@ -44,7 +44,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
||||||
const view = createRootView(services, viewDef);
|
const view = createRootView(services, () => viewDef);
|
||||||
const rootNodes = rootRenderNodes(view);
|
const rootNodes = rootRenderNodes(view);
|
||||||
return {rootNodes, view};
|
return {rootNodes, view};
|
||||||
}
|
}
|
||||||
@ -64,6 +64,28 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
expect(instances.length).toBe(1);
|
expect(instances.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add a DebugContext to errors in provider factories', () => {
|
||||||
|
class SomeService {
|
||||||
|
constructor() { throw new Error('Test'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
let err: any;
|
||||||
|
try {
|
||||||
|
createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(NodeFlags.None, null, 0, SomeService, [])
|
||||||
|
]));
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
expect(err).toBeTruthy();
|
||||||
|
expect(err.message).toBe('Test');
|
||||||
|
const debugCtx = <DebugContext>err.context;
|
||||||
|
expect(debugCtx.view).toBeTruthy();
|
||||||
|
// errors should point to the already existing element
|
||||||
|
expect(debugCtx.nodeIndex).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
describe('deps', () => {
|
describe('deps', () => {
|
||||||
let instance: SomeService;
|
let instance: SomeService;
|
||||||
class Dep {}
|
class Dep {}
|
||||||
@ -149,12 +171,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should inject ElementRef', () => {
|
it('should inject ElementRef', () => {
|
||||||
createAndGetRootNodes(compViewDef([
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, null, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.None, null, 0, SomeService, [ElementRef])
|
providerDef(NodeFlags.None, null, 0, SomeService, [ElementRef])
|
||||||
]));
|
]));
|
||||||
|
|
||||||
expect(getDOM().nodeName(instance.dep.nativeElement).toLowerCase()).toBe('span');
|
expect(instance.dep.nativeElement).toBe(asElementData(view, 0).renderElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (config.directDom) {
|
if (config.directDom) {
|
||||||
@ -181,16 +203,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('data binding', () => {
|
describe('data binding', () => {
|
||||||
[{
|
|
||||||
name: 'inline',
|
INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => {
|
||||||
update: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 1, 'v1', 'v2')
|
it(`should update ${InlineDynamic[inlineDynamic]}`, () => {
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dynamic',
|
|
||||||
update: (updater: NodeUpdater, view: ViewData) =>
|
|
||||||
updater.checkDynamic(view, 1, ['v1', 'v2'])
|
|
||||||
}].forEach((config) => {
|
|
||||||
it(`should update ${config.name}`, () => {
|
|
||||||
let instance: SomeService;
|
let instance: SomeService;
|
||||||
|
|
||||||
class SomeService {
|
class SomeService {
|
||||||
@ -204,35 +219,21 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
elementDef(NodeFlags.None, null, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.None, null, 0, SomeService, [], {a: [0, 'a'], b: [1, 'b']})
|
providerDef(NodeFlags.None, null, 0, SomeService, [], {a: [0, 'a'], b: [1, 'b']})
|
||||||
],
|
],
|
||||||
config.update));
|
(view) => {
|
||||||
|
setCurrentNode(view, 1);
|
||||||
|
checkNodeInlineOrDynamic(inlineDynamic, ['v1', 'v2']);
|
||||||
|
}));
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
expect(instance.a).toBe('v1');
|
expect(instance.a).toBe('v1');
|
||||||
expect(instance.b).toBe('v2');
|
expect(instance.b).toBe('v2');
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should checkNoChanges', () => {
|
if (!config.directDom) {
|
||||||
class SomeService {
|
const el = rootNodes[0];
|
||||||
a: any;
|
expect(getDOM().getAttribute(el, 'ng-reflect-a')).toBe('v1');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
let propValue = 'v1';
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
|
||||||
[
|
|
||||||
elementDef(NodeFlags.None, null, 1, 'span'),
|
|
||||||
providerDef(NodeFlags.None, null, 0, SomeService, [], {a: [0, 'a']})
|
|
||||||
],
|
|
||||||
(updater, view) => updater.checkInline(view, 1, propValue)));
|
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
|
||||||
checkNoChangesView(view);
|
|
||||||
|
|
||||||
propValue = 'v2';
|
|
||||||
expect(() => checkNoChangesView(view))
|
|
||||||
.toThrowError(
|
|
||||||
`Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -268,6 +269,34 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
destroyView(view);
|
destroyView(view);
|
||||||
expect(unsubscribeSpy).toHaveBeenCalled();
|
expect(unsubscribeSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should report debug info on event errors', () => {
|
||||||
|
let emitter = new EventEmitter<any>();
|
||||||
|
|
||||||
|
class SomeService {
|
||||||
|
emitter = emitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
|
[
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(
|
||||||
|
NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'})
|
||||||
|
],
|
||||||
|
null, () => { throw new Error('Test'); }));
|
||||||
|
|
||||||
|
let err: any;
|
||||||
|
try {
|
||||||
|
emitter.emit('someEventInstance');
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
expect(err).toBeTruthy();
|
||||||
|
const debugCtx = <DebugContext>err.context;
|
||||||
|
expect(debugCtx.view).toBe(view);
|
||||||
|
// events are emitted with the index of the element, not the index of the provider.
|
||||||
|
expect(debugCtx.nodeIndex).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('lifecycle hooks', () => {
|
describe('lifecycle hooks', () => {
|
||||||
@ -301,8 +330,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
providerDef(allFlags, null, 0, SomeService, [], {a: [0, 'a']})
|
providerDef(allFlags, null, 0, SomeService, [], {a: [0, 'a']})
|
||||||
],
|
],
|
||||||
(updater) => {
|
(updater) => {
|
||||||
updater.checkInline(view, 1, 'someValue');
|
setCurrentNode(view, 1);
|
||||||
updater.checkInline(view, 3, 'someValue');
|
checkNodeInline('someValue');
|
||||||
|
setCurrentNode(view, 3);
|
||||||
|
checkNodeInline('someValue');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
checkAndUpdateView(view);
|
||||||
@ -357,7 +388,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
elementDef(NodeFlags.None, null, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.OnChanges, null, 0, SomeService, [], {a: [0, 'nonMinifiedA']})
|
providerDef(NodeFlags.OnChanges, null, 0, SomeService, [], {a: [0, 'nonMinifiedA']})
|
||||||
],
|
],
|
||||||
(updater) => updater.checkInline(view, 1, currValue)));
|
(updater) => {
|
||||||
|
setCurrentNode(view, 1);
|
||||||
|
checkNodeInline(currValue);
|
||||||
|
}));
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
checkAndUpdateView(view);
|
||||||
expect(changesLog).toEqual([new SimpleChange(undefined, 'v1', true)]);
|
expect(changesLog).toEqual([new SimpleChange(undefined, 'v1', true)]);
|
||||||
@ -367,6 +401,52 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
checkAndUpdateView(view);
|
checkAndUpdateView(view);
|
||||||
expect(changesLog).toEqual([new SimpleChange('v1', 'v2', false)]);
|
expect(changesLog).toEqual([new SimpleChange('v1', 'v2', false)]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add a DebugContext to errors in provider afterXXX lifecycles', () => {
|
||||||
|
class SomeService implements AfterContentChecked {
|
||||||
|
ngAfterContentChecked() { throw new Error('Test'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(NodeFlags.AfterContentChecked, null, 0, SomeService, [], {a: [0, 'a']}),
|
||||||
|
]));
|
||||||
|
|
||||||
|
let err: any;
|
||||||
|
try {
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
expect(err).toBeTruthy();
|
||||||
|
expect(err.message).toBe('Test');
|
||||||
|
const debugCtx = <DebugContext>err.context;
|
||||||
|
expect(debugCtx.view).toBe(view);
|
||||||
|
expect(debugCtx.nodeIndex).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a DebugContext to errors in destroyView', () => {
|
||||||
|
class SomeService implements OnDestroy {
|
||||||
|
ngOnDestroy() { throw new Error('Test'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(NodeFlags.OnDestroy, null, 0, SomeService, [], {a: [0, 'a']}),
|
||||||
|
]));
|
||||||
|
|
||||||
|
let err: any;
|
||||||
|
try {
|
||||||
|
destroyView(view);
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
expect(err).toBeTruthy();
|
||||||
|
expect(err.message).toBe('Test');
|
||||||
|
const debugCtx = <DebugContext>err.context;
|
||||||
|
expect(debugCtx.view).toBe(view);
|
||||||
|
expect(debugCtx.nodeIndex).toBe(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
import {PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
||||||
import {DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, checkAndUpdateView, checkNoChangesView, createRootView, elementDef, providerDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
import {DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, elementDef, providerDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
import {inject} from '@angular/core/testing';
|
import {inject} from '@angular/core/testing';
|
||||||
|
|
||||||
import {INLINE_DYNAMIC_VALUES, InlineDynamic, callUpdater} from './helper';
|
import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic} from './helper';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe(`View Pure Expressions`, () => {
|
describe(`View Pure Expressions`, () => {
|
||||||
@ -30,7 +30,7 @@ export function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
||||||
const view = createRootView(services, viewDef);
|
const view = createRootView(services, () => viewDef);
|
||||||
const rootNodes = rootRenderNodes(view);
|
const rootNodes = rootRenderNodes(view);
|
||||||
return {rootNodes, view};
|
return {rootNodes, view};
|
||||||
}
|
}
|
||||||
@ -48,10 +48,11 @@ export function main() {
|
|||||||
elementDef(NodeFlags.None, null, 2, 'span'), pureArrayDef(2),
|
elementDef(NodeFlags.None, null, 2, 'span'), pureArrayDef(2),
|
||||||
providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
|
providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
|
||||||
],
|
],
|
||||||
(updater, view) => {
|
(view) => {
|
||||||
callUpdater(
|
setCurrentNode(view, 1);
|
||||||
updater, inlineDynamic, view, 2,
|
const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values);
|
||||||
[callUpdater(updater, inlineDynamic, view, 1, values)]);
|
setCurrentNode(view, 2);
|
||||||
|
checkNodeInlineOrDynamic(inlineDynamic, [pureValue]);
|
||||||
}));
|
}));
|
||||||
const service = asProviderData(view, 2).instance;
|
const service = asProviderData(view, 2).instance;
|
||||||
|
|
||||||
@ -82,10 +83,11 @@ export function main() {
|
|||||||
elementDef(NodeFlags.None, null, 2, 'span'), pureObjectDef(['a', 'b']),
|
elementDef(NodeFlags.None, null, 2, 'span'), pureObjectDef(['a', 'b']),
|
||||||
providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
|
providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
|
||||||
],
|
],
|
||||||
(updater, view) => {
|
(view) => {
|
||||||
callUpdater(
|
setCurrentNode(view, 1);
|
||||||
updater, inlineDynamic, view, 2,
|
const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values);
|
||||||
[callUpdater(updater, inlineDynamic, view, 1, values)]);
|
setCurrentNode(view, 2);
|
||||||
|
checkNodeInlineOrDynamic(inlineDynamic, [pureValue]);
|
||||||
}));
|
}));
|
||||||
const service = asProviderData(view, 2).instance;
|
const service = asProviderData(view, 2).instance;
|
||||||
|
|
||||||
@ -121,10 +123,11 @@ export function main() {
|
|||||||
providerDef(NodeFlags.None, null, 0, SomePipe, []), purePipeDef(SomePipe, 2),
|
providerDef(NodeFlags.None, null, 0, SomePipe, []), purePipeDef(SomePipe, 2),
|
||||||
providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
|
providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
|
||||||
],
|
],
|
||||||
(updater, view) => {
|
(view) => {
|
||||||
callUpdater(
|
setCurrentNode(view, 2);
|
||||||
updater, inlineDynamic, view, 3,
|
const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values);
|
||||||
[callUpdater(updater, inlineDynamic, view, 2, values)]);
|
setCurrentNode(view, 3);
|
||||||
|
checkNodeInlineOrDynamic(inlineDynamic, [pureValue]);
|
||||||
}));
|
}));
|
||||||
const service = asProviderData(view, 3).instance;
|
const service = asProviderData(view, 3).instance;
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ElementRef, QueryList, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
import {ElementRef, QueryList, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core';
|
||||||
import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, QueryBindingType, QueryValueType, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, attachEmbeddedView, checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView, detachEmbeddedView, elementDef, providerDef, queryDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
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, elementDef, providerDef, queryDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
import {inject} from '@angular/core/testing';
|
import {inject} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ export function main() {
|
|||||||
|
|
||||||
function createAndGetRootNodes(
|
function createAndGetRootNodes(
|
||||||
viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} {
|
viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} {
|
||||||
const view = createRootView(services, viewDef, context);
|
const view = createRootView(services, () => viewDef, context);
|
||||||
const rootNodes = rootRenderNodes(view);
|
const rootNodes = rootRenderNodes(view);
|
||||||
return {rootNodes, view};
|
return {rootNodes, view};
|
||||||
}
|
}
|
||||||
@ -197,30 +197,6 @@ export function main() {
|
|||||||
expect(qs2.a.length).toBe(0);
|
expect(qs2.a.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should checkNoChanges', () => {
|
|
||||||
const {view} = createAndGetRootNodes(compViewDef([
|
|
||||||
elementDef(NodeFlags.None, null, 4, 'div'),
|
|
||||||
...contentQueryProviders(),
|
|
||||||
anchorDef(
|
|
||||||
NodeFlags.HasEmbeddedViews, null, 1, viewDef(
|
|
||||||
ViewFlags.None,
|
|
||||||
[
|
|
||||||
elementDef(NodeFlags.None, null, 1, 'div'),
|
|
||||||
aServiceProvider(),
|
|
||||||
])),
|
|
||||||
]));
|
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
|
||||||
checkNoChangesView(view);
|
|
||||||
|
|
||||||
const childView = createEmbeddedView(view, view.def.nodes[3]);
|
|
||||||
attachEmbeddedView(asElementData(view, 3), 0, childView);
|
|
||||||
|
|
||||||
expect(() => checkNoChangesView(view))
|
|
||||||
.toThrowError(
|
|
||||||
`Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update content queries if embedded views are added or removed', () => {
|
it('should update content queries if embedded views are added or removed', () => {
|
||||||
const {view} = createAndGetRootNodes(compViewDef([
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, null, 3, 'div'),
|
elementDef(NodeFlags.None, null, 3, 'div'),
|
||||||
@ -383,5 +359,67 @@ export function main() {
|
|||||||
expect(qs.a.createEmbeddedView).toBeTruthy();
|
expect(qs.a.createEmbeddedView).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('general binding behavior', () => {
|
||||||
|
it('should checkNoChanges', () => {
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 4, 'div'),
|
||||||
|
...contentQueryProviders(),
|
||||||
|
anchorDef(
|
||||||
|
NodeFlags.HasEmbeddedViews, null, 1, viewDef(
|
||||||
|
ViewFlags.None,
|
||||||
|
[
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
|
aServiceProvider(),
|
||||||
|
])),
|
||||||
|
]));
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
checkNoChangesView(view);
|
||||||
|
|
||||||
|
const childView = createEmbeddedView(view, view.def.nodes[3]);
|
||||||
|
attachEmbeddedView(asElementData(view, 3), 0, childView);
|
||||||
|
|
||||||
|
let err: any;
|
||||||
|
try {
|
||||||
|
checkNoChangesView(view);
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
expect(err).toBeTruthy();
|
||||||
|
expect(err.message)
|
||||||
|
.toBe(
|
||||||
|
`Expression has changed after it was checked. Previous value: 'Query query1 not dirty'. Current value: 'Query query1 dirty'.`);
|
||||||
|
const debugCtx = <DebugContext>err.context;
|
||||||
|
expect(debugCtx.view).toBe(view);
|
||||||
|
expect(debugCtx.nodeIndex).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should report debug info on binding errors', () => {
|
||||||
|
class QueryService {
|
||||||
|
set a(value: any) { throw new Error('Test'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 3, 'div'),
|
||||||
|
providerDef(NodeFlags.None, null, 1, QueryService, []),
|
||||||
|
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All}),
|
||||||
|
aServiceProvider(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
|
||||||
|
let err: any;
|
||||||
|
try {
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
expect(err).toBeTruthy();
|
||||||
|
expect(err.message).toBe('Test');
|
||||||
|
const debugCtx = <DebugContext>err.context;
|
||||||
|
expect(debugCtx.view).toBe(view);
|
||||||
|
expect(debugCtx.nodeIndex).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
99
modules/@angular/core/test/view/services_spec.ts
Normal file
99
modules/@angular/core/test/view/services_spec.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* @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 {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, 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 {isBrowser, setupAndCheckRenderer} from './helper';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('View Services', () => {
|
||||||
|
let services: Services;
|
||||||
|
let renderComponentType: RenderComponentType;
|
||||||
|
|
||||||
|
beforeEach(
|
||||||
|
inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => {
|
||||||
|
services = new DefaultServices(rootRenderer, sanitizer);
|
||||||
|
renderComponentType =
|
||||||
|
new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {});
|
||||||
|
}));
|
||||||
|
|
||||||
|
function compViewDef(
|
||||||
|
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition {
|
||||||
|
return viewDef(ViewFlags.None, nodes, update, handleEvent, renderComponentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAndGetRootNodes(
|
||||||
|
viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} {
|
||||||
|
const view = createRootView(services, () => viewDef, context);
|
||||||
|
const rootNodes = rootRenderNodes(view);
|
||||||
|
return {rootNodes, view};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DebugContext', () => {
|
||||||
|
class AComp {}
|
||||||
|
|
||||||
|
class AService {}
|
||||||
|
|
||||||
|
function createViewWithData() {
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
|
providerDef(
|
||||||
|
NodeFlags.None, null, 0, AComp, [], null, null,
|
||||||
|
() => compViewDef([
|
||||||
|
elementDef(NodeFlags.None, [['#ref', QueryValueType.ElementRef]], 2, 'span'),
|
||||||
|
providerDef(NodeFlags.None, null, 0, AService, []), textDef(['a'])
|
||||||
|
])),
|
||||||
|
]));
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should provide data for elements', () => {
|
||||||
|
const view = createViewWithData();
|
||||||
|
const compView = asProviderData(view, 1).componentView;
|
||||||
|
|
||||||
|
const debugCtx = view.services.createDebugContext(compView, 0);
|
||||||
|
|
||||||
|
expect(debugCtx.componentRenderElement).toBe(asElementData(view, 0).renderElement);
|
||||||
|
expect(debugCtx.renderNode).toBe(asElementData(compView, 0).renderElement);
|
||||||
|
expect(debugCtx.injector.get(AComp)).toBe(compView.component);
|
||||||
|
expect(debugCtx.component).toBe(compView.component);
|
||||||
|
expect(debugCtx.context).toBe(compView.context);
|
||||||
|
expect(debugCtx.providerTokens).toEqual([AService]);
|
||||||
|
expect(debugCtx.source).toBeTruthy();
|
||||||
|
expect(debugCtx.references['ref'].nativeElement)
|
||||||
|
.toBe(asElementData(compView, 0).renderElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide data for text nodes', () => {
|
||||||
|
const view = createViewWithData();
|
||||||
|
const compView = asProviderData(view, 1).componentView;
|
||||||
|
|
||||||
|
const debugCtx = view.services.createDebugContext(compView, 2);
|
||||||
|
|
||||||
|
expect(debugCtx.componentRenderElement).toBe(asElementData(view, 0).renderElement);
|
||||||
|
expect(debugCtx.renderNode).toBe(asTextData(compView, 2).renderText);
|
||||||
|
expect(debugCtx.injector.get(AComp)).toBe(compView.component);
|
||||||
|
expect(debugCtx.component).toBe(compView.component);
|
||||||
|
expect(debugCtx.context).toBe(compView.context);
|
||||||
|
expect(debugCtx.source).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide data for other nodes based on the nearest element parent', () => {
|
||||||
|
const view = createViewWithData();
|
||||||
|
const compView = asProviderData(view, 1).componentView;
|
||||||
|
|
||||||
|
const debugCtx = view.services.createDebugContext(compView, 1);
|
||||||
|
|
||||||
|
expect(debugCtx.renderNode).toBe(asElementData(compView, 0).renderElement);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -6,12 +6,12 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, getDebugNode} from '@angular/core';
|
||||||
import {DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
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 {inject} from '@angular/core/testing';
|
import {inject} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
|
||||||
import {isBrowser, setupAndCheckRenderer} from './helper';
|
import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic, isBrowser, setupAndCheckRenderer} from './helper';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
if (isBrowser()) {
|
if (isBrowser()) {
|
||||||
@ -39,8 +39,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType);
|
return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
function createAndGetRootNodes(
|
||||||
const view = createRootView(services, viewDef);
|
viewDef: ViewDefinition, context?: any): {rootNodes: any[], view: ViewData} {
|
||||||
|
const view = createRootView(services, () => viewDef, context);
|
||||||
const rootNodes = rootRenderNodes(view);
|
const rootNodes = rootRenderNodes(view);
|
||||||
return {rootNodes, view};
|
return {rootNodes, view};
|
||||||
}
|
}
|
||||||
@ -67,41 +68,28 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
|||||||
const textNode = getDOM().firstChild(rootNodes[0]);
|
const textNode = getDOM().firstChild(rootNodes[0]);
|
||||||
expect(getDOM().getText(textNode)).toBe('a');
|
expect(getDOM().getText(textNode)).toBe('a');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!config.directDom) {
|
||||||
|
it('should add debug information to the renderer', () => {
|
||||||
|
const someContext = new Object();
|
||||||
|
const {view, rootNodes} =
|
||||||
|
createAndGetRootNodes(compViewDef([textDef(['a'])]), someContext);
|
||||||
|
expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asTextData(view, 0).renderText);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
it('should checkNoChanges', () => {
|
|
||||||
let textValue = 'v1';
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
|
||||||
[
|
|
||||||
textDef(['', '']),
|
|
||||||
],
|
|
||||||
(updater, view) => updater.checkInline(view, 0, textValue)));
|
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
|
||||||
checkNoChangesView(view);
|
|
||||||
|
|
||||||
textValue = 'v2';
|
|
||||||
expect(() => checkNoChangesView(view))
|
|
||||||
.toThrowError(
|
|
||||||
`Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('change text', () => {
|
describe('change text', () => {
|
||||||
[{
|
INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => {
|
||||||
name: 'inline',
|
it(`should update ${InlineDynamic[inlineDynamic]}`, () => {
|
||||||
update: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'a', 'b')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dynamic',
|
|
||||||
update: (updater: NodeUpdater, view: ViewData) =>
|
|
||||||
updater.checkDynamic(view, 0, ['a', 'b'])
|
|
||||||
}].forEach((config) => {
|
|
||||||
it(`should update ${config.name}`, () => {
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
textDef(['0', '1', '2']),
|
textDef(['0', '1', '2']),
|
||||||
],
|
],
|
||||||
config.update));
|
(view: ViewData) => {
|
||||||
|
setCurrentNode(view, 0);
|
||||||
|
checkNodeInlineOrDynamic(inlineDynamic, ['a', 'b']);
|
||||||
|
}));
|
||||||
|
|
||||||
checkAndUpdateView(view);
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {NodeFlags, NodeUpdater, QueryValueType, ViewData, ViewDefinition, ViewFlags, anchorDef, checkAndUpdateView, checkNoChangesView, elementDef, providerDef, textDef, viewDef} from '@angular/core/src/view/index';
|
import {NodeFlags, QueryValueType, ViewData, ViewDefinition, ViewFlags, anchorDef, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, elementDef, providerDef, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('viewDef', () => {
|
describe('viewDef', () => {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ApplicationRef, NgModuleRef} from '@angular/core';
|
import {ApplicationRef, NgModuleRef, enableProdMode} from '@angular/core';
|
||||||
|
|
||||||
import {bindAction, profile} from '../../util';
|
import {bindAction, profile} from '../../util';
|
||||||
import {buildTree, emptyTree} from '../util';
|
import {buildTree, emptyTree} from '../util';
|
||||||
@ -40,6 +40,7 @@ export function main() {
|
|||||||
|
|
||||||
const numberOfChecksEl = document.getElementById('numberOfChecks');
|
const numberOfChecksEl = document.getElementById('numberOfChecks');
|
||||||
|
|
||||||
|
enableProdMode();
|
||||||
appMod = new AppModule();
|
appMod = new AppModule();
|
||||||
appMod.bootstrap();
|
appMod.bootstrap();
|
||||||
tree = appMod.rootComp;
|
tree = appMod.rootComp;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import {NgIf} from '@angular/common';
|
import {NgIf} from '@angular/common';
|
||||||
import {Component, NgModule, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
import {Component, NgModule, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||||
import {BindingType, DefaultServices, NodeFlags, NodeUpdater, ViewData, ViewDefinition, ViewFlags, anchorDef, asElementData, asProviderData, checkAndUpdateView, createRootView, elementDef, providerDef, textDef, viewDef} from '@angular/core/src/view/index';
|
import {BindingType, DefaultServices, NodeFlags, ViewData, ViewDefinition, ViewFlags, anchorDef, asElementData, asProviderData, checkAndUpdateView, checkNodeInline, createRootView, elementDef, providerDef, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
import {DomSanitizer, DomSanitizerImpl, SafeStyle} from '@angular/platform-browser/src/security/dom_sanitization_service';
|
import {DomSanitizer, DomSanitizerImpl, SafeStyle} from '@angular/platform-browser/src/security/dom_sanitization_service';
|
||||||
|
|
||||||
import {TreeNode, emptyTree} from '../util';
|
import {TreeNode, emptyTree} from '../util';
|
||||||
@ -23,38 +23,41 @@ export class TreeComponent {
|
|||||||
|
|
||||||
let viewFlags = ViewFlags.DirectDom;
|
let viewFlags = ViewFlags.DirectDom;
|
||||||
|
|
||||||
const TreeComponent_Host: ViewDefinition = viewDef(viewFlags, [
|
function TreeComponent_Host(): ViewDefinition {
|
||||||
|
return viewDef(viewFlags, [
|
||||||
elementDef(NodeFlags.None, null, 1, 'tree'),
|
elementDef(NodeFlags.None, null, 1, 'tree'),
|
||||||
providerDef(NodeFlags.None, null, 0, TreeComponent, [], null, null, () => TreeComponent_0),
|
providerDef(NodeFlags.None, null, 0, TreeComponent, [], null, null, TreeComponent_0),
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
const TreeComponent_1: ViewDefinition = viewDef(
|
function TreeComponent_0(): ViewDefinition {
|
||||||
|
const TreeComponent_1: ViewDefinition = viewDef(
|
||||||
viewFlags,
|
viewFlags,
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, null, 1, 'tree'),
|
elementDef(NodeFlags.None, null, 1, 'tree'),
|
||||||
providerDef(
|
providerDef(
|
||||||
NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null,
|
NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null, TreeComponent_0),
|
||||||
() => TreeComponent_0),
|
|
||||||
],
|
],
|
||||||
(updater: NodeUpdater, view: ViewData) => {
|
(view: ViewData) => {
|
||||||
const cmp = view.component;
|
const cmp = view.component;
|
||||||
updater.checkInline(view, 1, cmp.data.left);
|
setCurrentNode(view, 1);
|
||||||
|
checkNodeInline(cmp.data.left);
|
||||||
});
|
});
|
||||||
|
|
||||||
const TreeComponent_2: ViewDefinition = viewDef(
|
const TreeComponent_2: ViewDefinition = viewDef(
|
||||||
viewFlags,
|
viewFlags,
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, null, 1, 'tree'),
|
elementDef(NodeFlags.None, null, 1, 'tree'),
|
||||||
providerDef(
|
providerDef(
|
||||||
NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null,
|
NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null, TreeComponent_0),
|
||||||
() => TreeComponent_0),
|
|
||||||
],
|
],
|
||||||
(updater: NodeUpdater, view: ViewData) => {
|
(view: ViewData) => {
|
||||||
const cmp = view.component;
|
const cmp = view.component;
|
||||||
updater.checkInline(view, 1, cmp.data.right);
|
setCurrentNode(view, 1);
|
||||||
|
checkNodeInline(cmp.data.right);
|
||||||
});
|
});
|
||||||
|
|
||||||
const TreeComponent_0: ViewDefinition = viewDef(
|
return viewDef(
|
||||||
viewFlags,
|
viewFlags,
|
||||||
[
|
[
|
||||||
elementDef(
|
elementDef(
|
||||||
@ -68,13 +71,18 @@ const TreeComponent_0: ViewDefinition = viewDef(
|
|||||||
providerDef(
|
providerDef(
|
||||||
NodeFlags.None, null, 0, NgIf, [ViewContainerRef, TemplateRef], {ngIf: [0, 'ngIf']}),
|
NodeFlags.None, null, 0, NgIf, [ViewContainerRef, TemplateRef], {ngIf: [0, 'ngIf']}),
|
||||||
],
|
],
|
||||||
(updater: NodeUpdater, view: ViewData) => {
|
(view: ViewData) => {
|
||||||
const cmp = view.component;
|
const cmp = view.component;
|
||||||
updater.checkInline(view, 0, cmp.bgColor);
|
setCurrentNode(view, 0);
|
||||||
updater.checkInline(view, 1, cmp.data.value);
|
checkNodeInline(cmp.bgColor);
|
||||||
updater.checkInline(view, 3, cmp.data.left != null);
|
setCurrentNode(view, 1);
|
||||||
updater.checkInline(view, 5, cmp.data.right != null);
|
checkNodeInline(cmp.data.value);
|
||||||
|
setCurrentNode(view, 3);
|
||||||
|
checkNodeInline(cmp.data.left != null);
|
||||||
|
setCurrentNode(view, 5);
|
||||||
|
checkNodeInline(cmp.data.right != null);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
public rootComp: TreeComponent;
|
public rootComp: TreeComponent;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user