feat(core): add query support to view engine
Part of #14013 closes #14084
This commit is contained in:
parent
fc8694ed11
commit
1e729d7ba2
|
@ -8,11 +8,16 @@
|
||||||
|
|
||||||
import {SecurityContext} from '../security';
|
import {SecurityContext} from '../security';
|
||||||
|
|
||||||
import {BindingDef, BindingType, DisposableFn, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewDefinition, ViewFlags} from './types';
|
import {BindingDef, BindingType, DisposableFn, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, ViewData, ViewDefinition, ViewFlags} from './types';
|
||||||
import {checkAndUpdateBinding, setBindingDebugInfo} from './util';
|
import {checkAndUpdateBinding, setBindingDebugInfo} from './util';
|
||||||
|
|
||||||
export function anchorDef(
|
export function anchorDef(
|
||||||
flags: NodeFlags, childCount: number, template?: ViewDefinition): NodeDef {
|
flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number,
|
||||||
|
template?: ViewDefinition): NodeDef {
|
||||||
|
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
|
||||||
|
if (matchedQueries) {
|
||||||
|
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
type: NodeType.Element,
|
type: NodeType.Element,
|
||||||
// will bet set by the view definition
|
// will bet set by the view definition
|
||||||
|
@ -20,12 +25,13 @@ export function anchorDef(
|
||||||
reverseChildIndex: undefined,
|
reverseChildIndex: undefined,
|
||||||
parent: undefined,
|
parent: undefined,
|
||||||
childFlags: undefined,
|
childFlags: undefined,
|
||||||
|
childMatchedQueries: undefined,
|
||||||
bindingIndex: undefined,
|
bindingIndex: undefined,
|
||||||
disposableIndex: undefined,
|
disposableIndex: undefined,
|
||||||
providerIndices: undefined,
|
providerIndices: undefined,
|
||||||
// regular values
|
// regular values
|
||||||
flags,
|
flags,
|
||||||
childCount,
|
matchedQueries: matchedQueryDefs, childCount,
|
||||||
bindings: [],
|
bindings: [],
|
||||||
disposableCount: 0,
|
disposableCount: 0,
|
||||||
element: {name: undefined, attrs: undefined, outputs: [], template},
|
element: {name: undefined, attrs: undefined, outputs: [], template},
|
||||||
|
@ -36,11 +42,16 @@ export function anchorDef(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function elementDef(
|
export function elementDef(
|
||||||
flags: NodeFlags, childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
|
flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, name: string,
|
||||||
|
fixedAttrs: {[name: string]: string} = {},
|
||||||
bindings?:
|
bindings?:
|
||||||
([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 {
|
||||||
|
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
|
||||||
|
if (matchedQueries) {
|
||||||
|
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
||||||
|
}
|
||||||
bindings = bindings || [];
|
bindings = bindings || [];
|
||||||
const bindingDefs = new Array(bindings.length);
|
const bindingDefs = new Array(bindings.length);
|
||||||
for (let i = 0; i < bindings.length; i++) {
|
for (let i = 0; i < bindings.length; i++) {
|
||||||
|
@ -81,12 +92,13 @@ export function elementDef(
|
||||||
reverseChildIndex: undefined,
|
reverseChildIndex: undefined,
|
||||||
parent: undefined,
|
parent: undefined,
|
||||||
childFlags: undefined,
|
childFlags: undefined,
|
||||||
|
childMatchedQueries: undefined,
|
||||||
bindingIndex: undefined,
|
bindingIndex: undefined,
|
||||||
disposableIndex: undefined,
|
disposableIndex: undefined,
|
||||||
providerIndices: undefined,
|
providerIndices: undefined,
|
||||||
// regular values
|
// regular values
|
||||||
flags,
|
flags,
|
||||||
childCount,
|
matchedQueries: matchedQueryDefs, childCount,
|
||||||
bindings: bindingDefs,
|
bindings: bindingDefs,
|
||||||
disposableCount: outputDefs.length,
|
disposableCount: outputDefs.length,
|
||||||
element: {name, attrs: fixedAttrs, outputs: outputDefs, template: undefined},
|
element: {name, attrs: fixedAttrs, outputs: outputDefs, template: undefined},
|
||||||
|
@ -150,8 +162,11 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): No
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
elementOrText:
|
elementOrText: {
|
||||||
{node: el, embeddedViews: (def.flags & NodeFlags.HasEmbeddedViews) ? [] : undefined},
|
node: el,
|
||||||
|
embeddedViews: (def.flags & NodeFlags.HasEmbeddedViews) ? [] : undefined,
|
||||||
|
projectedViews: undefined
|
||||||
|
},
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
pureExpression: undefined,
|
pureExpression: undefined,
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,12 +10,14 @@ 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';
|
||||||
import {ElementRef} from '../linker/element_ref';
|
import {ElementRef} from '../linker/element_ref';
|
||||||
|
import {ExpressionChangedAfterItHasBeenCheckedError} from '../linker/errors';
|
||||||
|
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 {Renderer} from '../render/api';
|
import {Renderer} from '../render/api';
|
||||||
|
|
||||||
import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderOutputDef, Services, ViewData, ViewDefinition, ViewFlags} from './types';
|
import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderOutputDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, ViewDefinition, ViewFlags} from './types';
|
||||||
import {checkAndUpdateBinding, checkAndUpdateBindingWithChange, setBindingDebugInfo} from './util';
|
import {checkAndUpdateBinding, checkAndUpdateBindingWithChange, declaredViewContainer, setBindingDebugInfo} from './util';
|
||||||
|
|
||||||
const _tokenKeyCache = new Map<any, string>();
|
const _tokenKeyCache = new Map<any, string>();
|
||||||
|
|
||||||
|
@ -25,9 +27,16 @@ const ViewContainerRefTokenKey = tokenKey(ViewContainerRef);
|
||||||
const TemplateRefTokenKey = tokenKey(TemplateRef);
|
const TemplateRefTokenKey = tokenKey(TemplateRef);
|
||||||
|
|
||||||
export function providerDef(
|
export function providerDef(
|
||||||
flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[],
|
flags: NodeFlags, matchedQueries: [string, QueryValueType][], ctor: any,
|
||||||
props?: {[name: string]: [number, string]}, outputs?: {[name: string]: string},
|
deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
|
||||||
component?: () => ViewDefinition): NodeDef {
|
outputs?: {[name: string]: string},
|
||||||
|
contentQueries?: {[name: string]: [string, QueryBindingType]}, component?: () => ViewDefinition,
|
||||||
|
viewQueries?: {[name: string]: [string, QueryBindingType]}, ): NodeDef {
|
||||||
|
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
|
||||||
|
if (matchedQueries) {
|
||||||
|
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
||||||
|
}
|
||||||
|
|
||||||
const bindings: BindingDef[] = [];
|
const bindings: BindingDef[] = [];
|
||||||
if (props) {
|
if (props) {
|
||||||
for (let prop in props) {
|
for (let prop in props) {
|
||||||
|
@ -57,9 +66,27 @@ export function providerDef(
|
||||||
}
|
}
|
||||||
return {flags, token, tokenKey: tokenKey(token)};
|
return {flags, token, tokenKey: tokenKey(token)};
|
||||||
});
|
});
|
||||||
|
const contentQueryDefs: QueryDef[] = [];
|
||||||
|
for (let propName in contentQueries) {
|
||||||
|
const [id, bindingType] = contentQueries[propName];
|
||||||
|
contentQueryDefs.push({id, propName, bindingType});
|
||||||
|
}
|
||||||
|
const viewQueryDefs: QueryDef[] = [];
|
||||||
|
for (let propName in viewQueries) {
|
||||||
|
const [id, bindingType] = viewQueries[propName];
|
||||||
|
viewQueryDefs.push({id, propName, bindingType});
|
||||||
|
}
|
||||||
|
|
||||||
if (component) {
|
if (component) {
|
||||||
flags = flags | NodeFlags.HasComponent;
|
flags = flags | NodeFlags.HasComponent;
|
||||||
}
|
}
|
||||||
|
if (contentQueryDefs.length) {
|
||||||
|
flags = flags | NodeFlags.HasContentQuery;
|
||||||
|
}
|
||||||
|
if (viewQueryDefs.length) {
|
||||||
|
flags = flags | NodeFlags.HasViewQuery;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: NodeType.Provider,
|
type: NodeType.Provider,
|
||||||
// will bet set by the view definition
|
// will bet set by the view definition
|
||||||
|
@ -67,17 +94,26 @@ export function providerDef(
|
||||||
reverseChildIndex: undefined,
|
reverseChildIndex: undefined,
|
||||||
parent: undefined,
|
parent: undefined,
|
||||||
childFlags: undefined,
|
childFlags: undefined,
|
||||||
|
childMatchedQueries: undefined,
|
||||||
bindingIndex: undefined,
|
bindingIndex: undefined,
|
||||||
disposableIndex: undefined,
|
disposableIndex: undefined,
|
||||||
providerIndices: undefined,
|
providerIndices: undefined,
|
||||||
// regular values
|
// regular values
|
||||||
flags,
|
flags,
|
||||||
|
matchedQueries: matchedQueryDefs,
|
||||||
childCount: 0, bindings,
|
childCount: 0, 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),
|
||||||
|
ctor,
|
||||||
|
deps: depDefs,
|
||||||
|
outputs: outputDefs,
|
||||||
|
contentQueries: contentQueryDefs,
|
||||||
|
viewQueries: viewQueryDefs, component
|
||||||
|
},
|
||||||
text: undefined,
|
text: undefined,
|
||||||
pureExpression: undefined
|
pureExpression: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,9 +137,21 @@ export function createProvider(view: ViewData, def: NodeDef, componentView: View
|
||||||
view.disposables[def.disposableIndex + i] = subscription.unsubscribe.bind(subscription);
|
view.disposables[def.disposableIndex + i] = subscription.unsubscribe.bind(subscription);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let queries: {[queryId: string]: QueryList<any>};
|
||||||
|
if (providerDef.contentQueries.length || providerDef.viewQueries.length) {
|
||||||
|
queries = {};
|
||||||
|
for (let i = 0; i < providerDef.contentQueries.length; i++) {
|
||||||
|
const def = providerDef.contentQueries[i];
|
||||||
|
queries[def.id] = new QueryList<any>();
|
||||||
|
}
|
||||||
|
for (let i = 0; i < providerDef.viewQueries.length; i++) {
|
||||||
|
const def = providerDef.viewQueries[i];
|
||||||
|
queries[def.id] = new QueryList<any>();
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
elementOrText: undefined,
|
elementOrText: undefined,
|
||||||
provider: {instance: provider, componentView: componentView},
|
provider: {instance: provider, componentView: componentView, queries},
|
||||||
pureExpression: undefined,
|
pureExpression: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -202,7 +250,7 @@ export function resolveDep(
|
||||||
if (elDef.parent != null) {
|
if (elDef.parent != null) {
|
||||||
elIndex = elDef.parent;
|
elIndex = elDef.parent;
|
||||||
} else {
|
} else {
|
||||||
elIndex = view.parentIndex;
|
elIndex = view.parentDiIndex;
|
||||||
view = view.parent;
|
view = view.parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,7 +276,7 @@ export function resolveDep(
|
||||||
return view.nodes[providerIndex].provider.instance;
|
return view.nodes[providerIndex].provider.instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elIndex = view.parentIndex;
|
elIndex = view.parentDiIndex;
|
||||||
view = view.parent;
|
view = view.parent;
|
||||||
}
|
}
|
||||||
return Injector.NULL.get(depDef.token, notFoundValue);
|
return Injector.NULL.get(depDef.token, notFoundValue);
|
||||||
|
@ -265,6 +313,153 @@ function checkAndUpdateProp(
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum QueryAction {
|
||||||
|
CheckNoChanges,
|
||||||
|
CheckAndUpdate,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function execContentQueriesAction(view: ViewData, action: QueryAction) {
|
||||||
|
if (!(view.def.nodeFlags & NodeFlags.HasContentQuery)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < view.nodes.length; i++) {
|
||||||
|
const nodeDef = view.def.nodes[i];
|
||||||
|
if (nodeDef.flags & NodeFlags.HasContentQuery) {
|
||||||
|
execContentQuery(view, nodeDef, action);
|
||||||
|
} else if ((nodeDef.childFlags & NodeFlags.HasContentQuery) === 0) {
|
||||||
|
// no child has a content query
|
||||||
|
// then skip the children
|
||||||
|
i += nodeDef.childCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateViewQueries(view: ViewData, action: QueryAction) {
|
||||||
|
if (!(view.def.nodeFlags & NodeFlags.HasViewQuery)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < view.nodes.length; i++) {
|
||||||
|
const nodeDef = view.def.nodes[i];
|
||||||
|
if (nodeDef.flags & NodeFlags.HasViewQuery) {
|
||||||
|
updateViewQuery(view, nodeDef, action);
|
||||||
|
} else if ((nodeDef.childFlags & NodeFlags.HasViewQuery) === 0) {
|
||||||
|
// no child has a view query
|
||||||
|
// then skip the children
|
||||||
|
i += nodeDef.childCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function execContentQuery(view: ViewData, nodeDef: NodeDef, action: QueryAction) {
|
||||||
|
const providerData = view.nodes[nodeDef.index].provider;
|
||||||
|
for (let i = 0; i < nodeDef.provider.contentQueries.length; i++) {
|
||||||
|
const queryDef = nodeDef.provider.contentQueries[i];
|
||||||
|
const queryId = queryDef.id;
|
||||||
|
const queryList = providerData.queries[queryId];
|
||||||
|
if (queryList.dirty) {
|
||||||
|
const elementDef = view.def.nodes[nodeDef.parent];
|
||||||
|
const newValues = calcQueryValues(
|
||||||
|
view, elementDef.index, elementDef.index + elementDef.childCount, queryId, []);
|
||||||
|
execQueryAction(view, providerData.instance, queryList, queryDef, newValues, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateViewQuery(view: ViewData, nodeDef: NodeDef, action: QueryAction) {
|
||||||
|
for (let i = 0; i < nodeDef.provider.viewQueries.length; i++) {
|
||||||
|
const queryDef = nodeDef.provider.viewQueries[i];
|
||||||
|
const queryId = queryDef.id;
|
||||||
|
const providerData = view.nodes[nodeDef.index].provider;
|
||||||
|
const queryList = providerData.queries[queryId];
|
||||||
|
if (queryList.dirty) {
|
||||||
|
const componentView = providerData.componentView;
|
||||||
|
const newValues =
|
||||||
|
calcQueryValues(componentView, 0, componentView.nodes.length - 1, queryId, []);
|
||||||
|
execQueryAction(view, providerData.instance, queryList, queryDef, newValues, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function execQueryAction(
|
||||||
|
view: ViewData, provider: any, queryList: QueryList<any>, queryDef: QueryDef, newValues: any[],
|
||||||
|
action: QueryAction) {
|
||||||
|
switch (action) {
|
||||||
|
case QueryAction.CheckAndUpdate:
|
||||||
|
queryList.reset(newValues);
|
||||||
|
let boundValue: any;
|
||||||
|
switch (queryDef.bindingType) {
|
||||||
|
case QueryBindingType.First:
|
||||||
|
boundValue = queryList.first;
|
||||||
|
break;
|
||||||
|
case QueryBindingType.All:
|
||||||
|
boundValue = queryList;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
provider[queryDef.propName] = boundValue;
|
||||||
|
break;
|
||||||
|
case QueryAction.CheckNoChanges:
|
||||||
|
// queries should always be non dirty when we go into checkNoChanges!
|
||||||
|
const oldValuesStr = queryList.toArray().map(v => stringify(v));
|
||||||
|
const newValuesStr = newValues.map(v => stringify(v));
|
||||||
|
throw new ExpressionChangedAfterItHasBeenCheckedError(
|
||||||
|
oldValuesStr, newValuesStr, view.firstChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcQueryValues(
|
||||||
|
view: ViewData, startIndex: number, endIndex: number, queryId: string, values: any[]): any[] {
|
||||||
|
const len = view.def.nodes.length;
|
||||||
|
for (let i = startIndex; i <= endIndex; i++) {
|
||||||
|
const nodeDef = view.def.nodes[i];
|
||||||
|
const queryValueType = <QueryValueType>nodeDef.matchedQueries[queryId];
|
||||||
|
if (queryValueType != null) {
|
||||||
|
// a match
|
||||||
|
let value: any;
|
||||||
|
switch (queryValueType) {
|
||||||
|
case QueryValueType.ElementRef:
|
||||||
|
value = new ElementRef(view.nodes[i].elementOrText.node);
|
||||||
|
break;
|
||||||
|
case QueryValueType.TemplateRef:
|
||||||
|
value = view.services.createTemplateRef(view, nodeDef);
|
||||||
|
break;
|
||||||
|
case QueryValueType.ViewContainerRef:
|
||||||
|
value = view.services.createViewContainerRef(view.nodes[i]);
|
||||||
|
break;
|
||||||
|
case QueryValueType.Provider:
|
||||||
|
value = view.nodes[i].provider.instance;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
if (nodeDef.flags & NodeFlags.HasEmbeddedViews &&
|
||||||
|
queryId in nodeDef.element.template.nodeMatchedQueries) {
|
||||||
|
// check embedded views that were attached at the place of their template.
|
||||||
|
const nodeData = view.nodes[i];
|
||||||
|
const embeddedViews = nodeData.elementOrText.embeddedViews;
|
||||||
|
for (let k = 0; k < embeddedViews.length; k++) {
|
||||||
|
const embeddedView = embeddedViews[k];
|
||||||
|
const dvc = declaredViewContainer(embeddedView);
|
||||||
|
if (dvc && dvc === nodeData) {
|
||||||
|
calcQueryValues(embeddedView, 0, embeddedView.nodes.length - 1, queryId, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const projectedViews = nodeData.elementOrText.projectedViews;
|
||||||
|
if (projectedViews) {
|
||||||
|
for (let k = 0; k < projectedViews.length; k++) {
|
||||||
|
const projectedView = projectedViews[k];
|
||||||
|
calcQueryValues(projectedView, 0, projectedView.nodes.length - 1, queryId, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(queryId in nodeDef.childMatchedQueries)) {
|
||||||
|
// If don't check descendants, skip the children.
|
||||||
|
// Or: no child matches the query, then skip the children as well.
|
||||||
|
i += nodeDef.childCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: NodeFlags) {
|
export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: NodeFlags) {
|
||||||
if (!(view.def.nodeFlags & lifecycles)) {
|
if (!(view.def.nodeFlags & lifecycles)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -44,11 +44,13 @@ function _pureExpressionDef(
|
||||||
reverseChildIndex: undefined,
|
reverseChildIndex: undefined,
|
||||||
parent: undefined,
|
parent: undefined,
|
||||||
childFlags: undefined,
|
childFlags: undefined,
|
||||||
|
childMatchedQueries: undefined,
|
||||||
bindingIndex: undefined,
|
bindingIndex: undefined,
|
||||||
disposableIndex: undefined,
|
disposableIndex: undefined,
|
||||||
providerIndices: undefined,
|
providerIndices: undefined,
|
||||||
// regular values
|
// regular values
|
||||||
flags: 0,
|
flags: 0,
|
||||||
|
matchedQueries: {},
|
||||||
childCount: 0, bindings,
|
childCount: 0, bindings,
|
||||||
disposableCount: 0,
|
disposableCount: 0,
|
||||||
element: undefined,
|
element: undefined,
|
||||||
|
|
|
@ -29,11 +29,13 @@ export function textDef(constants: string[]): NodeDef {
|
||||||
reverseChildIndex: undefined,
|
reverseChildIndex: undefined,
|
||||||
parent: undefined,
|
parent: undefined,
|
||||||
childFlags: undefined,
|
childFlags: undefined,
|
||||||
|
childMatchedQueries: undefined,
|
||||||
bindingIndex: undefined,
|
bindingIndex: undefined,
|
||||||
disposableIndex: undefined,
|
disposableIndex: undefined,
|
||||||
providerIndices: undefined,
|
providerIndices: undefined,
|
||||||
// regular values
|
// regular values
|
||||||
flags: 0,
|
flags: 0,
|
||||||
|
matchedQueries: {},
|
||||||
childCount: 0, bindings,
|
childCount: 0, bindings,
|
||||||
disposableCount: 0,
|
disposableCount: 0,
|
||||||
element: undefined,
|
element: undefined,
|
||||||
|
@ -55,7 +57,7 @@ export function createText(view: ViewData, renderHost: any, def: NodeDef): NodeD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
elementOrText: {node: renderNode, embeddedViews: undefined},
|
elementOrText: {node: renderNode, embeddedViews: undefined, projectedViews: undefined},
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
pureExpression: undefined
|
pureExpression: undefined
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {PipeTransform} from '../change_detection/change_detection';
|
import {PipeTransform} from '../change_detection/change_detection';
|
||||||
|
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, Renderer, RootRenderer} from '../render/api';
|
||||||
|
@ -36,6 +37,11 @@ export interface ViewDefinition {
|
||||||
lastRootNode: number;
|
lastRootNode: number;
|
||||||
bindingCount: number;
|
bindingCount: number;
|
||||||
disposableCount: number;
|
disposableCount: number;
|
||||||
|
/**
|
||||||
|
* ids of all queries that are matched by one of the nodes.
|
||||||
|
* This includes query ids from templates as well.
|
||||||
|
*/
|
||||||
|
nodeMatchedQueries: {[queryId: string]: boolean};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ViewUpdateFn = (updater: NodeUpdater, view: ViewData) => void;
|
export type ViewUpdateFn = (updater: NodeUpdater, view: ViewData) => void;
|
||||||
|
@ -69,11 +75,21 @@ export interface NodeDef {
|
||||||
childCount: number;
|
childCount: number;
|
||||||
/** aggregated NodeFlags for all children **/
|
/** aggregated NodeFlags for all children **/
|
||||||
childFlags: NodeFlags;
|
childFlags: NodeFlags;
|
||||||
|
|
||||||
providerIndices: {[tokenKey: string]: number};
|
providerIndices: {[tokenKey: string]: number};
|
||||||
bindingIndex: number;
|
bindingIndex: number;
|
||||||
bindings: BindingDef[];
|
bindings: BindingDef[];
|
||||||
disposableIndex: number;
|
disposableIndex: number;
|
||||||
disposableCount: number;
|
disposableCount: number;
|
||||||
|
/**
|
||||||
|
* ids and value types of all queries that are matched by this node.
|
||||||
|
*/
|
||||||
|
matchedQueries: {[queryId: string]: QueryValueType};
|
||||||
|
/**
|
||||||
|
* ids of all queries that are matched by one of the child nodes.
|
||||||
|
* This includes query ids from templates as well.
|
||||||
|
*/
|
||||||
|
childMatchedQueries: {[queryId: string]: boolean};
|
||||||
element: ElementDef;
|
element: ElementDef;
|
||||||
provider: ProviderDef;
|
provider: ProviderDef;
|
||||||
text: TextDef;
|
text: TextDef;
|
||||||
|
@ -84,7 +100,7 @@ export enum NodeType {
|
||||||
Element,
|
Element,
|
||||||
Text,
|
Text,
|
||||||
Provider,
|
Provider,
|
||||||
PureExpression
|
PureExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,6 +118,8 @@ export enum NodeFlags {
|
||||||
AfterViewChecked = 1 << 7,
|
AfterViewChecked = 1 << 7,
|
||||||
HasEmbeddedViews = 1 << 8,
|
HasEmbeddedViews = 1 << 8,
|
||||||
HasComponent = 1 << 9,
|
HasComponent = 1 << 9,
|
||||||
|
HasContentQuery = 1 << 10,
|
||||||
|
HasViewQuery = 1 << 11,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ElementDef {
|
export interface ElementDef {
|
||||||
|
@ -140,10 +158,30 @@ export interface ProviderDef {
|
||||||
ctor: any;
|
ctor: any;
|
||||||
deps: DepDef[];
|
deps: DepDef[];
|
||||||
outputs: ProviderOutputDef[];
|
outputs: ProviderOutputDef[];
|
||||||
|
contentQueries: QueryDef[];
|
||||||
|
viewQueries: QueryDef[];
|
||||||
// closure to allow recursive components
|
// closure to allow recursive components
|
||||||
component: () => ViewDefinition;
|
component: () => ViewDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface QueryDef {
|
||||||
|
id: string;
|
||||||
|
propName: string;
|
||||||
|
bindingType: QueryBindingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum QueryBindingType {
|
||||||
|
First,
|
||||||
|
All
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum QueryValueType {
|
||||||
|
ElementRef,
|
||||||
|
TemplateRef,
|
||||||
|
ViewContainerRef,
|
||||||
|
Provider
|
||||||
|
}
|
||||||
|
|
||||||
export interface TextDef { prefix: string; }
|
export interface TextDef { prefix: string; }
|
||||||
|
|
||||||
export interface PureExpressionDef {
|
export interface PureExpressionDef {
|
||||||
|
@ -190,6 +228,10 @@ export interface ViewData {
|
||||||
// index of parent element / anchor. Not the index
|
// index of parent element / anchor. Not the index
|
||||||
// of the provider with the component view.
|
// of the provider with the component view.
|
||||||
parentIndex: number;
|
parentIndex: number;
|
||||||
|
// for component views, this is the same as parentIndex.
|
||||||
|
// for embedded views, this is the index of the parent node
|
||||||
|
// that contains the view container.
|
||||||
|
parentDiIndex: number;
|
||||||
parent: ViewData;
|
parent: ViewData;
|
||||||
component: any;
|
component: any;
|
||||||
context: any;
|
context: any;
|
||||||
|
@ -214,11 +256,17 @@ export interface NodeData {
|
||||||
export interface ElementOrTextData {
|
export interface ElementOrTextData {
|
||||||
node: any;
|
node: any;
|
||||||
embeddedViews: ViewData[];
|
embeddedViews: ViewData[];
|
||||||
|
// views that have been created from the template
|
||||||
|
// of this element,
|
||||||
|
// but inserted into the embeddedViews of another element.
|
||||||
|
// By default, this is undefined.
|
||||||
|
projectedViews: ViewData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProviderData {
|
export interface ProviderData {
|
||||||
instance: any;
|
instance: any;
|
||||||
componentView: ViewData;
|
componentView: ViewData;
|
||||||
|
queries: {[queryId: string]: QueryList<any>};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PureExpressionData {
|
export interface PureExpressionData {
|
||||||
|
|
|
@ -60,3 +60,11 @@ export function checkAndUpdateBindingWithChange(
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function declaredViewContainer(view: ViewData) {
|
||||||
|
if (view.parent) {
|
||||||
|
const parentView = view.parent;
|
||||||
|
return parentView.nodes[view.parentIndex];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {ExpressionChangedAfterItHasBeenCheckedError} from '../linker/errors';
|
||||||
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 {callLifecycleHooksChildrenFirst, checkAndUpdateProviderDynamic, checkAndUpdateProviderInline, createProvider} from './provider';
|
import {QueryAction, callLifecycleHooksChildrenFirst, checkAndUpdateProviderDynamic, checkAndUpdateProviderInline, createProvider, execContentQueriesAction, updateViewQueries} from './provider';
|
||||||
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
|
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
|
||||||
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} from './types';
|
import {ElementDef, NodeData, NodeDef, NodeFlags, NodeType, NodeUpdater, ProviderData, ProviderDef, Services, TextDef, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn} from './types';
|
||||||
|
@ -30,6 +30,7 @@ export function viewDef(
|
||||||
let viewBindingCount = 0;
|
let viewBindingCount = 0;
|
||||||
let viewDisposableCount = 0;
|
let viewDisposableCount = 0;
|
||||||
let viewFlags = 0;
|
let viewFlags = 0;
|
||||||
|
let viewMatchedQueries: {[queryId: string]: boolean} = {};
|
||||||
let currentParent: NodeDef = null;
|
let currentParent: NodeDef = null;
|
||||||
let lastRootNode: NodeDef = null;
|
let lastRootNode: NodeDef = null;
|
||||||
for (let i = 0; i < nodesWithoutIndices.length; i++) {
|
for (let i = 0; i < nodesWithoutIndices.length; i++) {
|
||||||
|
@ -37,6 +38,7 @@ export function viewDef(
|
||||||
const newParent = nodes[currentParent.parent];
|
const newParent = nodes[currentParent.parent];
|
||||||
if (newParent) {
|
if (newParent) {
|
||||||
newParent.childFlags |= currentParent.childFlags;
|
newParent.childFlags |= currentParent.childFlags;
|
||||||
|
copyInto(currentParent.childMatchedQueries, newParent.childMatchedQueries);
|
||||||
}
|
}
|
||||||
currentParent = newParent;
|
currentParent = newParent;
|
||||||
}
|
}
|
||||||
|
@ -54,10 +56,15 @@ export function viewDef(
|
||||||
validateNode(currentParent, node);
|
validateNode(currentParent, node);
|
||||||
|
|
||||||
viewFlags |= node.flags;
|
viewFlags |= node.flags;
|
||||||
|
copyInto(node.matchedQueries, viewMatchedQueries);
|
||||||
viewBindingCount += node.bindings.length;
|
viewBindingCount += node.bindings.length;
|
||||||
viewDisposableCount += node.disposableCount;
|
viewDisposableCount += node.disposableCount;
|
||||||
if (currentParent) {
|
if (currentParent) {
|
||||||
currentParent.childFlags |= node.flags;
|
currentParent.childFlags |= node.flags;
|
||||||
|
copyInto(node.matchedQueries, currentParent.childMatchedQueries);
|
||||||
|
if (node.element && node.element.template) {
|
||||||
|
copyInto(node.element.template.nodeMatchedQueries, currentParent.childMatchedQueries);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentParent) {
|
if (!currentParent) {
|
||||||
|
@ -65,15 +72,29 @@ export function viewDef(
|
||||||
}
|
}
|
||||||
if (node.provider) {
|
if (node.provider) {
|
||||||
currentParent.providerIndices[node.provider.tokenKey] = i;
|
currentParent.providerIndices[node.provider.tokenKey] = i;
|
||||||
|
for (let k = 0; k < node.provider.contentQueries.length; k++) {
|
||||||
|
currentParent.providerIndices[node.provider.contentQueries[k].id] = i;
|
||||||
|
}
|
||||||
|
for (let k = 0; k < node.provider.viewQueries.length; k++) {
|
||||||
|
currentParent.providerIndices[node.provider.viewQueries[k].id] = i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (node.childCount) {
|
if (node.childCount) {
|
||||||
currentParent = node;
|
currentParent = node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
while (currentParent) {
|
||||||
|
const newParent = nodes[currentParent.parent];
|
||||||
|
if (newParent) {
|
||||||
|
newParent.childFlags |= currentParent.childFlags;
|
||||||
|
copyInto(currentParent.childMatchedQueries, newParent.childMatchedQueries);
|
||||||
|
}
|
||||||
|
currentParent = newParent;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodeFlags: viewFlags,
|
nodeFlags: viewFlags,
|
||||||
flags,
|
nodeMatchedQueries: viewMatchedQueries, flags,
|
||||||
nodes: nodes, reverseChildNodes,
|
nodes: nodes, reverseChildNodes,
|
||||||
update: update || NOOP,
|
update: update || NOOP,
|
||||||
handleEvent: handleEvent || NOOP, componentType,
|
handleEvent: handleEvent || NOOP, componentType,
|
||||||
|
@ -83,6 +104,12 @@ export function viewDef(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyInto(source: any, target: any) {
|
||||||
|
for (let prop in source) {
|
||||||
|
target[prop] = source[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function calculateReverseChildIndex(
|
function calculateReverseChildIndex(
|
||||||
currentParent: NodeDef, i: number, childCount: number, nodeCount: number) {
|
currentParent: NodeDef, i: number, childCount: number, nodeCount: number) {
|
||||||
// Notes about reverse child order:
|
// Notes about reverse child order:
|
||||||
|
@ -158,9 +185,7 @@ function cloneAndModifyNode(nodeDef: NodeDef, values: {
|
||||||
providerIndices: {[tokenKey: string]: number}
|
providerIndices: {[tokenKey: string]: number}
|
||||||
}): NodeDef {
|
}): NodeDef {
|
||||||
const clonedNode: NodeDef = <any>{};
|
const clonedNode: NodeDef = <any>{};
|
||||||
for (let prop in nodeDef) {
|
copyInto(nodeDef, clonedNode);
|
||||||
(<any>clonedNode)[prop] = (<any>nodeDef)[prop];
|
|
||||||
}
|
|
||||||
|
|
||||||
clonedNode.index = values.index;
|
clonedNode.index = values.index;
|
||||||
clonedNode.bindingIndex = values.bindingIndex;
|
clonedNode.bindingIndex = values.bindingIndex;
|
||||||
|
@ -170,25 +195,28 @@ function cloneAndModifyNode(nodeDef: NodeDef, values: {
|
||||||
clonedNode.providerIndices = values.providerIndices;
|
clonedNode.providerIndices = values.providerIndices;
|
||||||
// Note: We can't set the value immediately, as we need to walk the children first.
|
// Note: We can't set the value immediately, as we need to walk the children first.
|
||||||
clonedNode.childFlags = 0;
|
clonedNode.childFlags = 0;
|
||||||
|
clonedNode.childMatchedQueries = {};
|
||||||
return clonedNode;
|
return clonedNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
|
export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
|
||||||
// embedded views are seen as siblings to the anchor, so we need
|
// embedded views are seen as siblings to the anchor, so we need
|
||||||
// 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(parent.services, parent, anchorDef.parent, anchorDef.element.template);
|
const view = createView(
|
||||||
|
parent.services, parent, anchorDef.index, anchorDef.parent, anchorDef.element.template);
|
||||||
initView(view, null, parent.component, context);
|
initView(view, null, parent.component, context);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createRootView(services: Services, def: ViewDefinition, context?: any): ViewData {
|
export function createRootView(services: Services, def: ViewDefinition, context?: any): ViewData {
|
||||||
const view = createView(services, null, null, def);
|
const view = createView(services, null, null, null, def);
|
||||||
initView(view, null, context, context);
|
initView(view, null, context, context);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createView(
|
function createView(
|
||||||
services: Services, parent: ViewData, parentIndex: number, def: ViewDefinition): ViewData {
|
services: Services, parent: ViewData, parentIndex: number, parentDiIndex: number,
|
||||||
|
def: ViewDefinition): ViewData {
|
||||||
const nodes: NodeData[] = new Array(def.nodes.length);
|
const nodes: NodeData[] = new Array(def.nodes.length);
|
||||||
let renderer: Renderer;
|
let renderer: Renderer;
|
||||||
if (def.flags != null && (def.flags & ViewFlags.DirectDom)) {
|
if (def.flags != null && (def.flags & ViewFlags.DirectDom)) {
|
||||||
|
@ -201,6 +229,7 @@ function createView(
|
||||||
def,
|
def,
|
||||||
parent,
|
parent,
|
||||||
parentIndex,
|
parentIndex,
|
||||||
|
parentDiIndex,
|
||||||
context: undefined,
|
context: undefined,
|
||||||
component: undefined, nodes,
|
component: undefined, nodes,
|
||||||
firstChange: true, renderer, services,
|
firstChange: true, renderer, services,
|
||||||
|
@ -227,7 +256,7 @@ function initView(view: ViewData, renderHost: any, component: any, context: any)
|
||||||
case NodeType.Provider:
|
case NodeType.Provider:
|
||||||
let componentView: ViewData;
|
let componentView: ViewData;
|
||||||
if (nodeDef.provider.component) {
|
if (nodeDef.provider.component) {
|
||||||
componentView = createView(view.services, view, i, nodeDef.provider.component());
|
componentView = createView(view.services, view, i, i, nodeDef.provider.component());
|
||||||
}
|
}
|
||||||
nodeData = createProvider(view, nodeDef, componentView);
|
nodeData = createProvider(view, nodeDef, componentView);
|
||||||
break;
|
break;
|
||||||
|
@ -243,7 +272,9 @@ function initView(view: ViewData, renderHost: any, component: any, context: any)
|
||||||
export function checkNoChangesView(view: ViewData) {
|
export function checkNoChangesView(view: ViewData) {
|
||||||
view.def.update(CheckNoChanges, view);
|
view.def.update(CheckNoChanges, view);
|
||||||
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
|
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
|
||||||
|
execContentQueriesAction(view, QueryAction.CheckNoChanges);
|
||||||
execComponentViewsAction(view, ViewAction.CheckNoChanges);
|
execComponentViewsAction(view, ViewAction.CheckNoChanges);
|
||||||
|
updateViewQueries(view, QueryAction.CheckNoChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CheckNoChanges: NodeUpdater = {
|
const CheckNoChanges: NodeUpdater = {
|
||||||
|
@ -293,10 +324,12 @@ const CheckNoChanges: NodeUpdater = {
|
||||||
export function checkAndUpdateView(view: ViewData) {
|
export function checkAndUpdateView(view: ViewData) {
|
||||||
view.def.update(CheckAndUpdate, view);
|
view.def.update(CheckAndUpdate, view);
|
||||||
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
|
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
|
||||||
|
execContentQueriesAction(view, QueryAction.CheckAndUpdate);
|
||||||
|
|
||||||
callLifecycleHooksChildrenFirst(
|
callLifecycleHooksChildrenFirst(
|
||||||
view, NodeFlags.AfterContentChecked | (view.firstChange ? NodeFlags.AfterContentInit : 0));
|
view, NodeFlags.AfterContentChecked | (view.firstChange ? NodeFlags.AfterContentInit : 0));
|
||||||
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
|
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
|
||||||
|
updateViewQueries(view, QueryAction.CheckAndUpdate);
|
||||||
|
|
||||||
callLifecycleHooksChildrenFirst(
|
callLifecycleHooksChildrenFirst(
|
||||||
view, NodeFlags.AfterViewChecked | (view.firstChange ? NodeFlags.AfterViewInit : 0));
|
view, NodeFlags.AfterViewChecked | (view.firstChange ? NodeFlags.AfterViewInit : 0));
|
||||||
|
|
|
@ -7,18 +7,28 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {NodeData, NodeFlags, ViewData} from './types';
|
import {NodeData, NodeFlags, ViewData} from './types';
|
||||||
|
import {declaredViewContainer} from './util';
|
||||||
|
|
||||||
export function attachEmbeddedView(node: NodeData, viewIndex: number, view: ViewData) {
|
export function attachEmbeddedView(node: NodeData, viewIndex: number, view: ViewData) {
|
||||||
let embeddedViews = node.elementOrText.embeddedViews;
|
let embeddedViews = node.elementOrText.embeddedViews;
|
||||||
if (viewIndex == null) {
|
if (viewIndex == null) {
|
||||||
viewIndex = embeddedViews.length;
|
viewIndex = embeddedViews.length;
|
||||||
}
|
}
|
||||||
// perf: array.push is faster than array.splice!
|
addToArray(embeddedViews, viewIndex, view);
|
||||||
if (viewIndex >= embeddedViews.length) {
|
const dvc = declaredViewContainer(view);
|
||||||
embeddedViews.push(view);
|
if (dvc && dvc !== node) {
|
||||||
} else {
|
let projectedViews = dvc.elementOrText.projectedViews;
|
||||||
embeddedViews.splice(viewIndex, 0, view);
|
if (!projectedViews) {
|
||||||
|
projectedViews = dvc.elementOrText.projectedViews = [];
|
||||||
|
}
|
||||||
|
projectedViews.push(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let queryId in view.def.nodeMatchedQueries) {
|
||||||
|
dirtyParentQuery(queryId, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update rendering
|
||||||
const prevView = viewIndex > 0 ? embeddedViews[viewIndex - 1] : null;
|
const prevView = viewIndex > 0 ? embeddedViews[viewIndex - 1] : null;
|
||||||
const prevNode = prevView ? prevView.nodes[prevView.def.lastRootNode] : node;
|
const prevNode = prevView ? prevView.nodes[prevView.def.lastRootNode] : node;
|
||||||
const prevRenderNode = prevNode.elementOrText.node;
|
const prevRenderNode = prevNode.elementOrText.node;
|
||||||
|
@ -41,12 +51,19 @@ export function detachEmbeddedView(node: NodeData, viewIndex: number): ViewData
|
||||||
viewIndex = embeddedViews.length;
|
viewIndex = embeddedViews.length;
|
||||||
}
|
}
|
||||||
const view = embeddedViews[viewIndex];
|
const view = embeddedViews[viewIndex];
|
||||||
// perf: array.pop is faster than array.splice!
|
removeFromArray(embeddedViews, viewIndex);
|
||||||
if (viewIndex >= embeddedViews.length - 1) {
|
|
||||||
embeddedViews.pop();
|
const dvc = declaredViewContainer(view);
|
||||||
} else {
|
if (dvc && dvc !== node) {
|
||||||
embeddedViews.splice(viewIndex, 1);
|
const projectedViews = dvc.elementOrText.projectedViews;
|
||||||
|
removeFromArray(projectedViews, projectedViews.indexOf(view));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let queryId in view.def.nodeMatchedQueries) {
|
||||||
|
dirtyParentQuery(queryId, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update rendering
|
||||||
if (view.renderer) {
|
if (view.renderer) {
|
||||||
view.renderer.detachView(rootRenderNodes(view));
|
view.renderer.detachView(rootRenderNodes(view));
|
||||||
} else {
|
} else {
|
||||||
|
@ -59,6 +76,45 @@ export function detachEmbeddedView(node: NodeData, viewIndex: number): ViewData
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addToArray(arr: any[], index: number, value: any) {
|
||||||
|
// perf: array.push is faster than array.splice!
|
||||||
|
if (index >= arr.length) {
|
||||||
|
arr.push(value);
|
||||||
|
} else {
|
||||||
|
arr.splice(index, 0, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromArray(arr: any[], index: number) {
|
||||||
|
// perf: array.pop is faster than array.splice!
|
||||||
|
if (index >= arr.length - 1) {
|
||||||
|
arr.pop();
|
||||||
|
} else {
|
||||||
|
arr.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dirtyParentQuery(queryId: string, view: ViewData) {
|
||||||
|
let nodeIndex = view.parentIndex;
|
||||||
|
view = view.parent;
|
||||||
|
let providerIdx: number;
|
||||||
|
while (view) {
|
||||||
|
const nodeDef = view.def.nodes[nodeIndex];
|
||||||
|
providerIdx = nodeDef.providerIndices[queryId];
|
||||||
|
if (providerIdx != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nodeIndex = view.parentIndex;
|
||||||
|
view = view.parent;
|
||||||
|
}
|
||||||
|
if (!view) {
|
||||||
|
throw new Error(
|
||||||
|
`Illegal State: Tried to dirty parent query ${queryId} but the query could not be found!`);
|
||||||
|
}
|
||||||
|
const providerData = view.nodes[providerIdx].provider;
|
||||||
|
providerData.queries[queryId].setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
export function rootRenderNodes(view: ViewData): any[] {
|
export function rootRenderNodes(view: ViewData): any[] {
|
||||||
const renderNodes: any[] = [];
|
const renderNodes: any[] = [];
|
||||||
collectSiblingRenderNodes(view, 0, renderNodes);
|
collectSiblingRenderNodes(view, 0, renderNodes);
|
||||||
|
|
|
@ -48,21 +48,21 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
it('should create anchor nodes without parents', () => {
|
it('should create anchor nodes without parents', () => {
|
||||||
const rootNodes =
|
const rootNodes =
|
||||||
createAndGetRootNodes(compViewDef([anchorDef(NodeFlags.None, 0)])).rootNodes;
|
createAndGetRootNodes(compViewDef([anchorDef(NodeFlags.None, null, 0)])).rootNodes;
|
||||||
expect(rootNodes.length).toBe(1);
|
expect(rootNodes.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create views with multiple root anchor nodes', () => {
|
it('should create views with multiple root anchor nodes', () => {
|
||||||
const rootNodes = createAndGetRootNodes(compViewDef([
|
const rootNodes = createAndGetRootNodes(compViewDef([
|
||||||
anchorDef(NodeFlags.None, 0), anchorDef(NodeFlags.None, 0)
|
anchorDef(NodeFlags.None, null, 0), anchorDef(NodeFlags.None, null, 0)
|
||||||
])).rootNodes;
|
])).rootNodes;
|
||||||
expect(rootNodes.length).toBe(2);
|
expect(rootNodes.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create anchor nodes with parents', () => {
|
it('should create anchor nodes with parents', () => {
|
||||||
const rootNodes = createAndGetRootNodes(compViewDef([
|
const rootNodes = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'div'),
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
anchorDef(NodeFlags.None, 0),
|
anchorDef(NodeFlags.None, null, 0),
|
||||||
])).rootNodes;
|
])).rootNodes;
|
||||||
expect(getDOM().childNodes(rootNodes[0]).length).toBe(1);
|
expect(getDOM().childNodes(rootNodes[0]).length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,10 +52,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'div'),
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
providerDef(NodeFlags.None, AComp, [], null, null, () => compViewDef([
|
providerDef(
|
||||||
elementDef(NodeFlags.None, 0, 'span'),
|
NodeFlags.None, null, AComp, [], null, null, null,
|
||||||
])),
|
() => compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 0, 'span'),
|
||||||
|
])),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
const compView = view.nodes[1].provider.componentView;
|
const compView = view.nodes[1].provider.componentView;
|
||||||
|
@ -78,10 +80,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(
|
const {view, rootNodes} = createAndGetRootNodes(
|
||||||
compViewDef([
|
compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'div'),
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
providerDef(NodeFlags.None, AComp, [], null, null, () => compViewDef(
|
providerDef(NodeFlags.None, null, AComp, [], null, null, null, () => compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
|
elementDef(NodeFlags.None, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
|
||||||
], update
|
], update
|
||||||
)),
|
)),
|
||||||
], jasmine.createSpy('parentUpdater')));
|
], jasmine.createSpy('parentUpdater')));
|
||||||
|
@ -114,12 +116,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'div'),
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
providerDef(
|
providerDef(
|
||||||
NodeFlags.None, AComp, [], null, null,
|
NodeFlags.None, null, AComp, [], null, null, null,
|
||||||
() => compViewDef([
|
() => compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.OnDestroy, ChildProvider, [])
|
providerDef(NodeFlags.OnDestroy, null, ChildProvider, [])
|
||||||
])),
|
])),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
|
|
@ -47,24 +47,25 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
it('should create elements without parents', () => {
|
it('should create elements without parents', () => {
|
||||||
const rootNodes =
|
const rootNodes = createAndGetRootNodes(compViewDef([
|
||||||
createAndGetRootNodes(compViewDef([elementDef(NodeFlags.None, 0, 'span')])).rootNodes;
|
elementDef(NodeFlags.None, null, 0, 'span')
|
||||||
|
])).rootNodes;
|
||||||
expect(rootNodes.length).toBe(1);
|
expect(rootNodes.length).toBe(1);
|
||||||
expect(getDOM().nodeName(rootNodes[0]).toLowerCase()).toBe('span');
|
expect(getDOM().nodeName(rootNodes[0]).toLowerCase()).toBe('span');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create views with multiple root elements', () => {
|
it('should create views with multiple root elements', () => {
|
||||||
const rootNodes =
|
const rootNodes = createAndGetRootNodes(compViewDef([
|
||||||
createAndGetRootNodes(compViewDef([
|
elementDef(NodeFlags.None, null, 0, 'span'),
|
||||||
elementDef(NodeFlags.None, 0, 'span'), elementDef(NodeFlags.None, 0, 'span')
|
elementDef(NodeFlags.None, null, 0, 'span')
|
||||||
])).rootNodes;
|
])).rootNodes;
|
||||||
expect(rootNodes.length).toBe(2);
|
expect(rootNodes.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create elements with parents', () => {
|
it('should create elements with parents', () => {
|
||||||
const rootNodes = createAndGetRootNodes(compViewDef([
|
const rootNodes = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'div'),
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
elementDef(NodeFlags.None, 0, 'span'),
|
elementDef(NodeFlags.None, null, 0, 'span'),
|
||||||
])).rootNodes;
|
])).rootNodes;
|
||||||
expect(rootNodes.length).toBe(1);
|
expect(rootNodes.length).toBe(1);
|
||||||
const spanEl = getDOM().childNodes(rootNodes[0])[0];
|
const spanEl = getDOM().childNodes(rootNodes[0])[0];
|
||||||
|
@ -73,7 +74,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
it('should set fixed attributes', () => {
|
it('should set fixed attributes', () => {
|
||||||
const rootNodes = createAndGetRootNodes(compViewDef([
|
const rootNodes = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 0, 'div', {'title': 'a'}),
|
elementDef(NodeFlags.None, null, 0, 'div', {'title': 'a'}),
|
||||||
])).rootNodes;
|
])).rootNodes;
|
||||||
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');
|
||||||
|
@ -85,7 +86,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(
|
elementDef(
|
||||||
NodeFlags.None, 0, 'div', null,
|
NodeFlags.None, null, 0, 'div', null,
|
||||||
[[BindingType.ElementAttribute, 'a1', SecurityContext.NONE]]),
|
[[BindingType.ElementAttribute, 'a1', SecurityContext.NONE]]),
|
||||||
],
|
],
|
||||||
(updater, view) => updater.checkInline(view, 0, attrValue)));
|
(updater, view) => updater.checkInline(view, 0, attrValue)));
|
||||||
|
@ -114,7 +115,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(
|
elementDef(
|
||||||
NodeFlags.None, 0, 'input', null,
|
NodeFlags.None, null, 0, 'input', null,
|
||||||
[
|
[
|
||||||
[BindingType.ElementProperty, 'title', SecurityContext.NONE],
|
[BindingType.ElementProperty, 'title', SecurityContext.NONE],
|
||||||
[BindingType.ElementProperty, 'value', SecurityContext.NONE]
|
[BindingType.ElementProperty, 'value', SecurityContext.NONE]
|
||||||
|
@ -145,7 +146,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(
|
elementDef(
|
||||||
NodeFlags.None, 0, 'div', null,
|
NodeFlags.None, null, 0, 'div', null,
|
||||||
[
|
[
|
||||||
[BindingType.ElementAttribute, 'a1', SecurityContext.NONE],
|
[BindingType.ElementAttribute, 'a1', SecurityContext.NONE],
|
||||||
[BindingType.ElementAttribute, 'a2', SecurityContext.NONE]
|
[BindingType.ElementAttribute, 'a2', SecurityContext.NONE]
|
||||||
|
@ -176,7 +177,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(
|
elementDef(
|
||||||
NodeFlags.None, 0, 'div', null,
|
NodeFlags.None, null, 0, 'div', null,
|
||||||
[[BindingType.ElementClass, 'c1'], [BindingType.ElementClass, 'c2']]),
|
[[BindingType.ElementClass, 'c1'], [BindingType.ElementClass, 'c2']]),
|
||||||
],
|
],
|
||||||
config.updater));
|
config.updater));
|
||||||
|
@ -204,7 +205,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(
|
elementDef(
|
||||||
NodeFlags.None, 0, 'div', null,
|
NodeFlags.None, null, 0, 'div', null,
|
||||||
[
|
[
|
||||||
[BindingType.ElementStyle, 'width', 'px'],
|
[BindingType.ElementStyle, 'width', 'px'],
|
||||||
[BindingType.ElementStyle, 'color', null]
|
[BindingType.ElementStyle, 'color', null]
|
||||||
|
@ -250,7 +251,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
const removeListenerSpy =
|
const removeListenerSpy =
|
||||||
spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough();
|
spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough();
|
||||||
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
||||||
[elementDef(NodeFlags.None, 0, 'button', null, null, ['click'])], null,
|
[elementDef(NodeFlags.None, null, 0, 'button', null, null, ['click'])], null,
|
||||||
handleEventSpy));
|
handleEventSpy));
|
||||||
|
|
||||||
rootNodes[0].click();
|
rootNodes[0].click();
|
||||||
|
@ -272,7 +273,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
const addListenerSpy = spyOn(window, 'addEventListener');
|
const addListenerSpy = spyOn(window, 'addEventListener');
|
||||||
const removeListenerSpy = spyOn(window, 'removeEventListener');
|
const removeListenerSpy = spyOn(window, 'removeEventListener');
|
||||||
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
||||||
[elementDef(NodeFlags.None, 0, 'button', null, null, [['window', 'windowClick']])],
|
[elementDef(
|
||||||
|
NodeFlags.None, null, 0, 'button', null, null, [['window', 'windowClick']])],
|
||||||
null, handleEventSpy));
|
null, handleEventSpy));
|
||||||
|
|
||||||
expect(addListenerSpy).toHaveBeenCalled();
|
expect(addListenerSpy).toHaveBeenCalled();
|
||||||
|
@ -297,7 +299,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
const removeListenerSpy = spyOn(document, 'removeEventListener');
|
const removeListenerSpy = spyOn(document, 'removeEventListener');
|
||||||
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
||||||
[elementDef(
|
[elementDef(
|
||||||
NodeFlags.None, 0, 'button', null, null, [['document', 'documentClick']])],
|
NodeFlags.None, null, 0, 'button', null, null, [['document', 'documentClick']])],
|
||||||
null, handleEventSpy));
|
null, handleEventSpy));
|
||||||
|
|
||||||
expect(addListenerSpy).toHaveBeenCalled();
|
expect(addListenerSpy).toHaveBeenCalled();
|
||||||
|
@ -321,7 +323,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
let preventDefaultSpy: jasmine.Spy;
|
let preventDefaultSpy: jasmine.Spy;
|
||||||
|
|
||||||
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
|
||||||
[elementDef(NodeFlags.None, 0, 'button', null, null, ['click'])], null,
|
[elementDef(NodeFlags.None, null, 0, 'button', null, null, ['click'])], null,
|
||||||
(view, index, eventName, event) => {
|
(view, index, eventName, event) => {
|
||||||
preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough();
|
preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough();
|
||||||
return eventHandlerResult;
|
return eventHandlerResult;
|
||||||
|
|
|
@ -56,9 +56,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
const {view: parentView, rootNodes} = createAndGetRootNodes(
|
const {view: parentView, rootNodes} = createAndGetRootNodes(
|
||||||
compViewDef([
|
compViewDef([
|
||||||
elementDef(NodeFlags.None, 2, 'div'),
|
elementDef(NodeFlags.None, null, 2, 'div'),
|
||||||
anchorDef(NodeFlags.HasEmbeddedViews, 0, embeddedViewDef([elementDef(
|
anchorDef(NodeFlags.HasEmbeddedViews, null, 0, embeddedViewDef([elementDef(
|
||||||
NodeFlags.None, 0, 'span')])),
|
NodeFlags.None, null, 0, 'span')])),
|
||||||
]),
|
]),
|
||||||
parentContext);
|
parentContext);
|
||||||
|
|
||||||
|
@ -69,12 +69,13 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
it('should attach and detach embedded views', () => {
|
it('should attach and detach embedded views', () => {
|
||||||
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
|
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 2, 'div'),
|
elementDef(NodeFlags.None, null, 2, 'div'),
|
||||||
anchorDef(
|
anchorDef(
|
||||||
NodeFlags.HasEmbeddedViews, 0,
|
NodeFlags.HasEmbeddedViews, null, 0,
|
||||||
embeddedViewDef([elementDef(NodeFlags.None, 0, 'span', {'name': 'child0'})])),
|
embeddedViewDef([elementDef(NodeFlags.None, null, 0, 'span', {'name': 'child0'})])),
|
||||||
anchorDef(NodeFlags.None, 0, embeddedViewDef([elementDef(
|
anchorDef(
|
||||||
NodeFlags.None, 0, 'span', {'name': 'child1'})]))
|
NodeFlags.None, null, 0,
|
||||||
|
embeddedViewDef([elementDef(NodeFlags.None, null, 0, 'span', {'name': 'child1'})]))
|
||||||
]));
|
]));
|
||||||
|
|
||||||
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
|
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
|
||||||
|
@ -99,9 +100,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
it('should include embedded views in root nodes', () => {
|
it('should include embedded views in root nodes', () => {
|
||||||
const {view: parentView} = createAndGetRootNodes(compViewDef([
|
const {view: parentView} = createAndGetRootNodes(compViewDef([
|
||||||
anchorDef(
|
anchorDef(
|
||||||
NodeFlags.HasEmbeddedViews, 0,
|
NodeFlags.HasEmbeddedViews, null, 0,
|
||||||
embeddedViewDef([elementDef(NodeFlags.None, 0, 'span', {'name': 'child0'})])),
|
embeddedViewDef([elementDef(NodeFlags.None, null, 0, 'span', {'name': 'child0'})])),
|
||||||
elementDef(NodeFlags.None, 0, 'span', {'name': 'after'})
|
elementDef(NodeFlags.None, null, 0, 'span', {'name': 'after'})
|
||||||
]));
|
]));
|
||||||
|
|
||||||
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[0]);
|
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[0]);
|
||||||
|
@ -119,12 +120,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
(updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, childValue));
|
(updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, childValue));
|
||||||
|
|
||||||
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
|
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'div'),
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
anchorDef(
|
anchorDef(
|
||||||
NodeFlags.HasEmbeddedViews, 0,
|
NodeFlags.HasEmbeddedViews, null, 0,
|
||||||
embeddedViewDef(
|
embeddedViewDef(
|
||||||
[elementDef(
|
[elementDef(
|
||||||
NodeFlags.None, 0, 'span', null,
|
NodeFlags.None, null, 0, 'span', null,
|
||||||
[[BindingType.ElementAttribute, 'name', SecurityContext.NONE]])],
|
[[BindingType.ElementAttribute, 'name', SecurityContext.NONE]])],
|
||||||
update))
|
update))
|
||||||
]));
|
]));
|
||||||
|
@ -159,10 +160,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
|
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'div'),
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
anchorDef(NodeFlags.HasEmbeddedViews, 0, embeddedViewDef([
|
anchorDef(NodeFlags.HasEmbeddedViews, null, 0, embeddedViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.OnDestroy, ChildProvider, [])
|
providerDef(NodeFlags.OnDestroy, null, ChildProvider, [])
|
||||||
]))
|
]))
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
constructor() { instances.push(this); }
|
constructor() { instances.push(this); }
|
||||||
}
|
}
|
||||||
|
|
||||||
createAndGetRootNodes(compViewDef(
|
createAndGetRootNodes(compViewDef([
|
||||||
[elementDef(NodeFlags.None, 1, 'span'), providerDef(NodeFlags.None, SomeService, [])]));
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(NodeFlags.None, null, SomeService, [])
|
||||||
|
]));
|
||||||
|
|
||||||
expect(instances.length).toBe(1);
|
expect(instances.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
@ -74,8 +76,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
it('should inject deps from the same element', () => {
|
it('should inject deps from the same element', () => {
|
||||||
createAndGetRootNodes(compViewDef([
|
createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 2, 'span'), providerDef(NodeFlags.None, Dep, []),
|
elementDef(NodeFlags.None, null, 2, 'span'), providerDef(NodeFlags.None, null, Dep, []),
|
||||||
providerDef(NodeFlags.None, SomeService, [Dep])
|
providerDef(NodeFlags.None, null, SomeService, [Dep])
|
||||||
]));
|
]));
|
||||||
|
|
||||||
expect(instance.dep instanceof Dep).toBeTruthy();
|
expect(instance.dep instanceof Dep).toBeTruthy();
|
||||||
|
@ -83,8 +85,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
it('should inject deps from a parent element', () => {
|
it('should inject deps from a parent element', () => {
|
||||||
createAndGetRootNodes(compViewDef([
|
createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 3, 'span'), providerDef(NodeFlags.None, Dep, []),
|
elementDef(NodeFlags.None, null, 3, 'span'), providerDef(NodeFlags.None, null, Dep, []),
|
||||||
elementDef(NodeFlags.None, 1, 'span'), providerDef(NodeFlags.None, SomeService, [Dep])
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(NodeFlags.None, null, SomeService, [Dep])
|
||||||
]));
|
]));
|
||||||
|
|
||||||
expect(instance.dep instanceof Dep).toBeTruthy();
|
expect(instance.dep instanceof Dep).toBeTruthy();
|
||||||
|
@ -92,8 +95,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
it('should not inject deps from sibling root elements', () => {
|
it('should not inject deps from sibling root elements', () => {
|
||||||
const nodes = [
|
const nodes = [
|
||||||
elementDef(NodeFlags.None, 1, 'span'), providerDef(NodeFlags.None, Dep, []),
|
elementDef(NodeFlags.None, null, 1, 'span'), providerDef(NodeFlags.None, null, Dep, []),
|
||||||
elementDef(NodeFlags.None, 1, 'span'), providerDef(NodeFlags.None, SomeService, [Dep])
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(NodeFlags.None, null, SomeService, [Dep])
|
||||||
];
|
];
|
||||||
|
|
||||||
// root elements
|
// root elements
|
||||||
|
@ -103,18 +107,18 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
// non root elements
|
// non root elements
|
||||||
expect(
|
expect(
|
||||||
() => createAndGetRootNodes(
|
() => createAndGetRootNodes(
|
||||||
compViewDef([elementDef(NodeFlags.None, 4, 'span')].concat(nodes))))
|
compViewDef([elementDef(NodeFlags.None, null, 4, 'span')].concat(nodes))))
|
||||||
.toThrowError('No provider for Dep!');
|
.toThrowError('No provider for Dep!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should inject from a parent elment in a parent view', () => {
|
it('should inject from a parent elment in a parent view', () => {
|
||||||
createAndGetRootNodes(compViewDef([
|
createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'div'),
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
providerDef(
|
providerDef(
|
||||||
NodeFlags.None, Dep, [], null, null,
|
NodeFlags.None, null, Dep, [], null, null, null,
|
||||||
() => compViewDef([
|
() => compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.None, SomeService, [Dep])
|
providerDef(NodeFlags.None, null, SomeService, [Dep])
|
||||||
])),
|
])),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
@ -124,8 +128,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
describe('builtin tokens', () => {
|
describe('builtin tokens', () => {
|
||||||
it('should inject ViewContainerRef', () => {
|
it('should inject ViewContainerRef', () => {
|
||||||
createAndGetRootNodes(compViewDef([
|
createAndGetRootNodes(compViewDef([
|
||||||
anchorDef(NodeFlags.HasEmbeddedViews, 1),
|
anchorDef(NodeFlags.HasEmbeddedViews, null, 1),
|
||||||
providerDef(NodeFlags.None, SomeService, [ViewContainerRef])
|
providerDef(NodeFlags.None, null, SomeService, [ViewContainerRef])
|
||||||
]));
|
]));
|
||||||
|
|
||||||
expect(instance.dep.createEmbeddedView).toBeTruthy();
|
expect(instance.dep.createEmbeddedView).toBeTruthy();
|
||||||
|
@ -133,8 +137,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
it('should inject TemplateRef', () => {
|
it('should inject TemplateRef', () => {
|
||||||
createAndGetRootNodes(compViewDef([
|
createAndGetRootNodes(compViewDef([
|
||||||
anchorDef(NodeFlags.None, 1, embeddedViewDef([anchorDef(NodeFlags.None, 0)])),
|
anchorDef(
|
||||||
providerDef(NodeFlags.None, SomeService, [TemplateRef])
|
NodeFlags.None, null, 1, embeddedViewDef([anchorDef(NodeFlags.None, null, 0)])),
|
||||||
|
providerDef(NodeFlags.None, null, SomeService, [TemplateRef])
|
||||||
]));
|
]));
|
||||||
|
|
||||||
expect(instance.dep.createEmbeddedView).toBeTruthy();
|
expect(instance.dep.createEmbeddedView).toBeTruthy();
|
||||||
|
@ -142,8 +147,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
it('should inject ElementRef', () => {
|
it('should inject ElementRef', () => {
|
||||||
createAndGetRootNodes(compViewDef([
|
createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.None, SomeService, [ElementRef])
|
providerDef(NodeFlags.None, null, SomeService, [ElementRef])
|
||||||
]));
|
]));
|
||||||
|
|
||||||
expect(getDOM().nodeName(instance.dep.nativeElement).toLowerCase()).toBe('span');
|
expect(getDOM().nodeName(instance.dep.nativeElement).toLowerCase()).toBe('span');
|
||||||
|
@ -152,16 +157,16 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
if (config.directDom) {
|
if (config.directDom) {
|
||||||
it('should not inject Renderer when using directDom', () => {
|
it('should not inject Renderer when using directDom', () => {
|
||||||
expect(() => createAndGetRootNodes(compViewDef([
|
expect(() => createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.None, SomeService, [Renderer])
|
providerDef(NodeFlags.None, null, SomeService, [Renderer])
|
||||||
])))
|
])))
|
||||||
.toThrowError('No provider for Renderer!');
|
.toThrowError('No provider for Renderer!');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
it('should inject Renderer when not using directDom', () => {
|
it('should inject Renderer when not using directDom', () => {
|
||||||
createAndGetRootNodes(compViewDef([
|
createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.None, SomeService, [Renderer])
|
providerDef(NodeFlags.None, null, SomeService, [Renderer])
|
||||||
]));
|
]));
|
||||||
|
|
||||||
expect(instance.dep.createElement).toBeTruthy();
|
expect(instance.dep.createElement).toBeTruthy();
|
||||||
|
@ -193,8 +198,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.None, SomeService, [], {a: [0, 'a'], b: [1, 'b']})
|
providerDef(NodeFlags.None, null, SomeService, [], {a: [0, 'a'], b: [1, 'b']})
|
||||||
],
|
],
|
||||||
config.update));
|
config.update));
|
||||||
|
|
||||||
|
@ -213,8 +218,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
let propValue = 'v1';
|
let propValue = 'v1';
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.None, SomeService, [], {a: [0, 'a']})
|
providerDef(NodeFlags.None, null, SomeService, [], {a: [0, 'a']})
|
||||||
],
|
],
|
||||||
(updater, view) => updater.checkInline(view, 1, propValue)));
|
(updater, view) => updater.checkInline(view, 1, propValue)));
|
||||||
|
|
||||||
|
@ -248,8 +253,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.None, SomeService, [], null, {emitter: 'someEventName'})
|
providerDef(NodeFlags.None, null, SomeService, [], null, {emitter: 'someEventName'})
|
||||||
],
|
],
|
||||||
null, handleEvent));
|
null, handleEvent));
|
||||||
|
|
||||||
|
@ -286,10 +291,10 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
NodeFlags.AfterViewChecked | NodeFlags.OnDestroy;
|
NodeFlags.AfterViewChecked | NodeFlags.OnDestroy;
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, 3, 'span'),
|
elementDef(NodeFlags.None, null, 3, 'span'),
|
||||||
providerDef(allFlags, SomeService, [], {a: [0, 'a']}),
|
providerDef(allFlags, null, SomeService, [], {a: [0, 'a']}),
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(allFlags, SomeService, [], {a: [0, 'a']})
|
providerDef(allFlags, null, SomeService, [], {a: [0, 'a']})
|
||||||
],
|
],
|
||||||
(updater) => {
|
(updater) => {
|
||||||
updater.checkInline(view, 1, 'someValue');
|
updater.checkInline(view, 1, 'someValue');
|
||||||
|
@ -345,8 +350,8 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.OnChanges, SomeService, [], {a: [0, 'nonMinifiedA']})
|
providerDef(NodeFlags.OnChanges, null, SomeService, [], {a: [0, 'nonMinifiedA']})
|
||||||
],
|
],
|
||||||
(updater) => updater.checkInline(view, 1, currValue)));
|
(updater) => updater.checkInline(view, 1, currValue)));
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,8 @@ export function main() {
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, 2, 'span'), pureArrayDef(2),
|
elementDef(NodeFlags.None, null, 2, 'span'), pureArrayDef(2),
|
||||||
providerDef(NodeFlags.None, Service, [], {data: [0, 'data']})
|
providerDef(NodeFlags.None, null, Service, [], {data: [0, 'data']})
|
||||||
],
|
],
|
||||||
(updater, view) => {
|
(updater, view) => {
|
||||||
callUpdater(
|
callUpdater(
|
||||||
|
@ -79,8 +79,8 @@ export function main() {
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, 2, 'span'), pureObjectDef(['a', 'b']),
|
elementDef(NodeFlags.None, null, 2, 'span'), pureObjectDef(['a', 'b']),
|
||||||
providerDef(NodeFlags.None, Service, [], {data: [0, 'data']})
|
providerDef(NodeFlags.None, null, Service, [], {data: [0, 'data']})
|
||||||
],
|
],
|
||||||
(updater, view) => {
|
(updater, view) => {
|
||||||
callUpdater(
|
callUpdater(
|
||||||
|
@ -117,9 +117,9 @@ export function main() {
|
||||||
|
|
||||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, 3, 'span'), providerDef(NodeFlags.None, SomePipe, []),
|
elementDef(NodeFlags.None, null, 3, 'span'),
|
||||||
purePipeDef(SomePipe, 2),
|
providerDef(NodeFlags.None, null, SomePipe, []), purePipeDef(SomePipe, 2),
|
||||||
providerDef(NodeFlags.None, Service, [], {data: [0, 'data']})
|
providerDef(NodeFlags.None, null, Service, [], {data: [0, 'data']})
|
||||||
],
|
],
|
||||||
(updater, view) => {
|
(updater, view) => {
|
||||||
callUpdater(
|
callUpdater(
|
||||||
|
|
|
@ -0,0 +1,390 @@
|
||||||
|
/**
|
||||||
|
* @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 {ElementRef, QueryList, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||||
|
import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, QueryBindingType, QueryValueType, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, attachEmbeddedView, checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView, detachEmbeddedView, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
|
import {inject} from '@angular/core/testing';
|
||||||
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe(`Query Views`, () => {
|
||||||
|
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 embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinition {
|
||||||
|
return viewDef(ViewFlags.None, nodes, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAndGetRootNodes(
|
||||||
|
viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} {
|
||||||
|
const view = createRootView(services, viewDef, context);
|
||||||
|
const rootNodes = rootRenderNodes(view);
|
||||||
|
return {rootNodes, view};
|
||||||
|
}
|
||||||
|
|
||||||
|
class AService {}
|
||||||
|
|
||||||
|
class QueryService {
|
||||||
|
a: QueryList<AService>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function contentQueryProvider() {
|
||||||
|
return providerDef(
|
||||||
|
NodeFlags.None, null, QueryService, [], null, null,
|
||||||
|
{'a': ['query1', QueryBindingType.All]});
|
||||||
|
}
|
||||||
|
|
||||||
|
function aServiceProvider() {
|
||||||
|
return providerDef(NodeFlags.None, [['query1', QueryValueType.Provider]], AService, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewQueryProvider(compView: ViewDefinition) {
|
||||||
|
return providerDef(
|
||||||
|
NodeFlags.None, null, QueryService, [], null, null, null, () => compView,
|
||||||
|
{'a': ['query1', QueryBindingType.All]});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('content queries', () => {
|
||||||
|
|
||||||
|
it('should query providers on the same element and child elements', () => {
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 4, 'div'),
|
||||||
|
contentQueryProvider(),
|
||||||
|
aServiceProvider(),
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
|
aServiceProvider(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
const qs: QueryService = view.nodes[1].provider.instance;
|
||||||
|
expect(qs.a).toBeUndefined();
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
const as = qs.a.toArray();
|
||||||
|
expect(as.length).toBe(2);
|
||||||
|
expect(as[0]).toBe(view.nodes[2].provider.instance);
|
||||||
|
expect(as[1]).toBe(view.nodes[4].provider.instance);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not query providers on sibling or parent elements', () => {
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 5, 'div'),
|
||||||
|
aServiceProvider(),
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
|
contentQueryProvider(),
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
|
aServiceProvider(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
const qs: QueryService = view.nodes[3].provider.instance;
|
||||||
|
expect(qs.a.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('view queries', () => {
|
||||||
|
it('should query providers in the view', () => {
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
|
viewQueryProvider(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
aServiceProvider(),
|
||||||
|
])),
|
||||||
|
]));
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
const comp: QueryService = view.nodes[1].provider.instance;
|
||||||
|
const compView = view.nodes[1].provider.componentView;
|
||||||
|
expect(comp.a.length).toBe(1);
|
||||||
|
expect(comp.a.first).toBe(compView.nodes[1].provider.instance);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not query providers on the host element', () => {
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 2, 'div'),
|
||||||
|
viewQueryProvider(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
])),
|
||||||
|
aServiceProvider(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
const comp: QueryService = view.nodes[1].provider.instance;
|
||||||
|
expect(comp.a.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('embedded views', () => {
|
||||||
|
it('should query providers in embedded views', () => {
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 3, 'div'),
|
||||||
|
contentQueryProvider(),
|
||||||
|
anchorDef(
|
||||||
|
NodeFlags.HasEmbeddedViews, null, 1, viewDef(
|
||||||
|
ViewFlags.None,
|
||||||
|
[
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
|
aServiceProvider(),
|
||||||
|
])),
|
||||||
|
contentQueryProvider(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
const childView = createEmbeddedView(view, view.def.nodes[2]);
|
||||||
|
attachEmbeddedView(view.nodes[2], 0, childView);
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
// queries on parent elements of anchors
|
||||||
|
const qs1: QueryService = view.nodes[1].provider.instance;
|
||||||
|
expect(qs1.a.length).toBe(1);
|
||||||
|
expect(qs1.a.first instanceof AService).toBe(true);
|
||||||
|
|
||||||
|
// queries on the anchor
|
||||||
|
const qs2: QueryService = view.nodes[3].provider.instance;
|
||||||
|
expect(qs2.a.length).toBe(1);
|
||||||
|
expect(qs2.a.first instanceof AService).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should query providers in embedded views only at the template declaration', () => {
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 2, 'div'),
|
||||||
|
contentQueryProvider(),
|
||||||
|
anchorDef(
|
||||||
|
NodeFlags.HasEmbeddedViews, null, 0, viewDef(
|
||||||
|
ViewFlags.None,
|
||||||
|
[
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
|
aServiceProvider(),
|
||||||
|
])),
|
||||||
|
elementDef(NodeFlags.None, null, 2, 'div'),
|
||||||
|
contentQueryProvider(),
|
||||||
|
anchorDef(NodeFlags.HasEmbeddedViews, null, 0),
|
||||||
|
]));
|
||||||
|
|
||||||
|
const childView = createEmbeddedView(view, view.def.nodes[2]);
|
||||||
|
// attach at a different place than the one where the template was defined
|
||||||
|
attachEmbeddedView(view.nodes[5], 0, childView);
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
// query on the declaration place
|
||||||
|
const qs1: QueryService = view.nodes[1].provider.instance;
|
||||||
|
expect(qs1.a.length).toBe(1);
|
||||||
|
expect(qs1.a.first instanceof AService).toBe(true);
|
||||||
|
|
||||||
|
// query on the attach place
|
||||||
|
const qs2: QueryService = view.nodes[4].provider.instance;
|
||||||
|
expect(qs2.a.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should checkNoChanges', () => {
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 3, 'div'),
|
||||||
|
contentQueryProvider(),
|
||||||
|
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[2]);
|
||||||
|
attachEmbeddedView(view.nodes[2], 0, childView);
|
||||||
|
|
||||||
|
expect(() => checkNoChangesView(view))
|
||||||
|
.toThrowError(
|
||||||
|
`Expression has changed after it was checked. Previous value: ''. Current value: '[object Object]'.`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update content queries if embedded views are added or removed', () => {
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 2, 'div'),
|
||||||
|
contentQueryProvider(),
|
||||||
|
anchorDef(
|
||||||
|
NodeFlags.HasEmbeddedViews, null, 0, viewDef(
|
||||||
|
ViewFlags.None,
|
||||||
|
[
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
|
aServiceProvider(),
|
||||||
|
])),
|
||||||
|
]));
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
const qs: QueryService = view.nodes[1].provider.instance;
|
||||||
|
expect(qs.a.length).toBe(0);
|
||||||
|
|
||||||
|
const childView = createEmbeddedView(view, view.def.nodes[2]);
|
||||||
|
attachEmbeddedView(view.nodes[2], 0, childView);
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
expect(qs.a.length).toBe(1);
|
||||||
|
|
||||||
|
detachEmbeddedView(view.nodes[2], 0);
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
expect(qs.a.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update view queries if embedded views are added or removed', () => {
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
|
viewQueryProvider(compViewDef([
|
||||||
|
anchorDef(
|
||||||
|
NodeFlags.HasEmbeddedViews, null, 0,
|
||||||
|
viewDef(
|
||||||
|
ViewFlags.None,
|
||||||
|
[
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
|
aServiceProvider(),
|
||||||
|
])),
|
||||||
|
])),
|
||||||
|
]));
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
const comp: QueryService = view.nodes[1].provider.instance;
|
||||||
|
expect(comp.a.length).toBe(0);
|
||||||
|
|
||||||
|
const compView = view.nodes[1].provider.componentView;
|
||||||
|
const childView = createEmbeddedView(compView, compView.def.nodes[0]);
|
||||||
|
attachEmbeddedView(compView.nodes[0], 0, childView);
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
expect(comp.a.length).toBe(1);
|
||||||
|
|
||||||
|
detachEmbeddedView(compView.nodes[0], 0);
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
expect(comp.a.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('QueryBindingType', () => {
|
||||||
|
it('should query all matches', () => {
|
||||||
|
class QueryService {
|
||||||
|
a: QueryList<AService>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 3, 'div'),
|
||||||
|
providerDef(
|
||||||
|
NodeFlags.None, null, QueryService, [], null, null,
|
||||||
|
{'a': ['query1', QueryBindingType.All]}),
|
||||||
|
aServiceProvider(),
|
||||||
|
aServiceProvider(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
const qs: QueryService = view.nodes[1].provider.instance;
|
||||||
|
expect(qs.a instanceof QueryList).toBeTruthy();
|
||||||
|
expect(qs.a.toArray()).toEqual([
|
||||||
|
view.nodes[2].provider.instance,
|
||||||
|
view.nodes[3].provider.instance,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should query the first match', () => {
|
||||||
|
class QueryService {
|
||||||
|
a: AService;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, null, 3, 'div'),
|
||||||
|
providerDef(
|
||||||
|
NodeFlags.None, null, QueryService, [], null, null,
|
||||||
|
{'a': ['query1', QueryBindingType.First]}),
|
||||||
|
aServiceProvider(),
|
||||||
|
aServiceProvider(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
const qs: QueryService = view.nodes[1].provider.instance;
|
||||||
|
expect(qs.a).toBe(view.nodes[2].provider.instance);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('query builtins', () => {
|
||||||
|
it('should query ElementRef', () => {
|
||||||
|
class QueryService {
|
||||||
|
a: ElementRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
elementDef(NodeFlags.None, [['query1', QueryValueType.ElementRef]], 1, 'div'),
|
||||||
|
providerDef(
|
||||||
|
NodeFlags.None, null, QueryService, [], null, null,
|
||||||
|
{'a': ['query1', QueryBindingType.First]}),
|
||||||
|
]));
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
const qs: QueryService = view.nodes[1].provider.instance;
|
||||||
|
expect(qs.a.nativeElement).toBe(view.nodes[0].elementOrText.node);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should query TemplateRef', () => {
|
||||||
|
class QueryService {
|
||||||
|
a: TemplateRef<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
anchorDef(
|
||||||
|
NodeFlags.None, [['query1', QueryValueType.TemplateRef]], 1,
|
||||||
|
viewDef(ViewFlags.None, [anchorDef(NodeFlags.None, null, 0)])),
|
||||||
|
providerDef(
|
||||||
|
NodeFlags.None, null, QueryService, [], null, null,
|
||||||
|
{'a': ['query1', QueryBindingType.First]}),
|
||||||
|
]));
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
const qs: QueryService = view.nodes[1].provider.instance;
|
||||||
|
expect(qs.a.createEmbeddedView).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should query ViewContainerRef', () => {
|
||||||
|
class QueryService {
|
||||||
|
a: ViewContainerRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {view} = createAndGetRootNodes(compViewDef([
|
||||||
|
anchorDef(NodeFlags.None, [['query1', QueryValueType.ViewContainerRef]], 1),
|
||||||
|
providerDef(
|
||||||
|
NodeFlags.None, null, QueryService, [], null, null,
|
||||||
|
{'a': ['query1', QueryBindingType.First]}),
|
||||||
|
]));
|
||||||
|
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
|
||||||
|
const qs: QueryService = view.nodes[1].provider.instance;
|
||||||
|
expect(qs.a.createEmbeddedView).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -60,7 +60,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||||
|
|
||||||
it('should create text nodes with parents', () => {
|
it('should create text nodes with parents', () => {
|
||||||
const rootNodes = createAndGetRootNodes(compViewDef([
|
const rootNodes = createAndGetRootNodes(compViewDef([
|
||||||
elementDef(NodeFlags.None, 1, 'div'),
|
elementDef(NodeFlags.None, null, 1, 'div'),
|
||||||
textDef(['a']),
|
textDef(['a']),
|
||||||
])).rootNodes;
|
])).rootNodes;
|
||||||
expect(rootNodes.length).toBe(1);
|
expect(rootNodes.length).toBe(1);
|
||||||
|
|
|
@ -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, ViewData, ViewDefinition, ViewFlags, anchorDef, checkAndUpdateView, checkNoChangesView, elementDef, providerDef, textDef, viewDef} from '@angular/core/src/view/index';
|
import {NodeFlags, NodeUpdater, QueryValueType, ViewData, ViewDefinition, ViewFlags, anchorDef, checkAndUpdateView, checkNoChangesView, elementDef, providerDef, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('viewDef', () => {
|
describe('viewDef', () => {
|
||||||
|
@ -26,9 +26,9 @@ export function main() {
|
||||||
|
|
||||||
it('should reverse child order for one level, one root', () => {
|
it('should reverse child order for one level, one root', () => {
|
||||||
const vd = viewDef(ViewFlags.None, [
|
const vd = viewDef(ViewFlags.None, [
|
||||||
elementDef(NodeFlags.None, 2, 'span'), // level 0, index 0
|
elementDef(NodeFlags.None, null, 2, 'span'), // level 0, index 0
|
||||||
textDef(['a']), // level 1, index 1
|
textDef(['a']), // level 1, index 1
|
||||||
textDef(['a']), // level 1, index 2
|
textDef(['a']), // level 1, index 2
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(reverseChildOrder(vd)).toEqual([0, 2, 1]);
|
expect(reverseChildOrder(vd)).toEqual([0, 2, 1]);
|
||||||
|
@ -36,11 +36,11 @@ export function main() {
|
||||||
|
|
||||||
it('should reverse child order for 1 level, 2 roots', () => {
|
it('should reverse child order for 1 level, 2 roots', () => {
|
||||||
const vd = viewDef(ViewFlags.None, [
|
const vd = viewDef(ViewFlags.None, [
|
||||||
elementDef(NodeFlags.None, 2, 'span'), // level 0, index 0
|
elementDef(NodeFlags.None, null, 2, 'span'), // level 0, index 0
|
||||||
textDef(['a']), // level 1, index 1
|
textDef(['a']), // level 1, index 1
|
||||||
textDef(['a']), // level 1, index 2
|
textDef(['a']), // level 1, index 2
|
||||||
elementDef(NodeFlags.None, 1, 'span'), // level 0, index 3
|
elementDef(NodeFlags.None, null, 1, 'span'), // level 0, index 3
|
||||||
textDef(['a']), // level 1, index 4
|
textDef(['a']), // level 1, index 4
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(reverseChildOrder(vd)).toEqual([3, 4, 0, 2, 1]);
|
expect(reverseChildOrder(vd)).toEqual([3, 4, 0, 2, 1]);
|
||||||
|
@ -48,11 +48,11 @@ export function main() {
|
||||||
|
|
||||||
it('should reverse child order for 2 levels', () => {
|
it('should reverse child order for 2 levels', () => {
|
||||||
const vd = viewDef(ViewFlags.None, [
|
const vd = viewDef(ViewFlags.None, [
|
||||||
elementDef(NodeFlags.None, 4, 'span'), // level 0, index 0
|
elementDef(NodeFlags.None, null, 4, 'span'), // level 0, index 0
|
||||||
elementDef(NodeFlags.None, 1, 'span'), // level 1, index 1
|
elementDef(NodeFlags.None, null, 1, 'span'), // level 1, index 1
|
||||||
textDef(['a']), // level 2, index 2
|
textDef(['a']), // level 2, index 2
|
||||||
elementDef(NodeFlags.None, 1, 'span'), // level 1, index 3
|
elementDef(NodeFlags.None, null, 1, 'span'), // level 1, index 3
|
||||||
textDef(['a']), // level 2, index 4
|
textDef(['a']), // level 2, index 4
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(reverseChildOrder(vd)).toEqual([0, 3, 4, 1, 2]);
|
expect(reverseChildOrder(vd)).toEqual([0, 3, 4, 1, 2]);
|
||||||
|
@ -60,14 +60,14 @@ export function main() {
|
||||||
|
|
||||||
it('should reverse child order for mixed levels', () => {
|
it('should reverse child order for mixed levels', () => {
|
||||||
const vd = viewDef(ViewFlags.None, [
|
const vd = viewDef(ViewFlags.None, [
|
||||||
textDef(['a']), // level 0, index 0
|
textDef(['a']), // level 0, index 0
|
||||||
elementDef(NodeFlags.None, 5, 'span'), // level 0, index 1
|
elementDef(NodeFlags.None, null, 5, 'span'), // level 0, index 1
|
||||||
textDef(['a']), // level 1, index 2
|
textDef(['a']), // level 1, index 2
|
||||||
elementDef(NodeFlags.None, 1, 'span'), // level 1, index 3
|
elementDef(NodeFlags.None, null, 1, 'span'), // level 1, index 3
|
||||||
textDef(['a']), // level 2, index 4
|
textDef(['a']), // level 2, index 4
|
||||||
elementDef(NodeFlags.None, 1, 'span'), // level 1, index 5
|
elementDef(NodeFlags.None, null, 1, 'span'), // level 1, index 5
|
||||||
textDef(['a']), // level 2, index 6
|
textDef(['a']), // level 2, index 6
|
||||||
textDef(['a']), // level 0, index 7
|
textDef(['a']), // level 0, index 7
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(reverseChildOrder(vd)).toEqual([7, 1, 5, 6, 3, 4, 2, 0]);
|
expect(reverseChildOrder(vd)).toEqual([7, 1, 5, 6, 3, 4, 2, 0]);
|
||||||
|
@ -81,7 +81,7 @@ export function main() {
|
||||||
|
|
||||||
it('should calculate parents for one level', () => {
|
it('should calculate parents for one level', () => {
|
||||||
const vd = viewDef(ViewFlags.None, [
|
const vd = viewDef(ViewFlags.None, [
|
||||||
elementDef(NodeFlags.None, 2, 'span'),
|
elementDef(NodeFlags.None, null, 2, 'span'),
|
||||||
textDef(['a']),
|
textDef(['a']),
|
||||||
textDef(['a']),
|
textDef(['a']),
|
||||||
]);
|
]);
|
||||||
|
@ -91,9 +91,9 @@ export function main() {
|
||||||
|
|
||||||
it('should calculate parents for one level, multiple roots', () => {
|
it('should calculate parents for one level, multiple roots', () => {
|
||||||
const vd = viewDef(ViewFlags.None, [
|
const vd = viewDef(ViewFlags.None, [
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
textDef(['a']),
|
textDef(['a']),
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
textDef(['a']),
|
textDef(['a']),
|
||||||
textDef(['a']),
|
textDef(['a']),
|
||||||
]);
|
]);
|
||||||
|
@ -103,10 +103,10 @@ export function main() {
|
||||||
|
|
||||||
it('should calculate parents for multiple levels', () => {
|
it('should calculate parents for multiple levels', () => {
|
||||||
const vd = viewDef(ViewFlags.None, [
|
const vd = viewDef(ViewFlags.None, [
|
||||||
elementDef(NodeFlags.None, 2, 'span'),
|
elementDef(NodeFlags.None, null, 2, 'span'),
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
textDef(['a']),
|
textDef(['a']),
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
textDef(['a']),
|
textDef(['a']),
|
||||||
textDef(['a']),
|
textDef(['a']),
|
||||||
]);
|
]);
|
||||||
|
@ -123,20 +123,31 @@ export function main() {
|
||||||
|
|
||||||
it('should calculate childFlags for one level', () => {
|
it('should calculate childFlags for one level', () => {
|
||||||
const vd = viewDef(ViewFlags.None, [
|
const vd = viewDef(ViewFlags.None, [
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.AfterContentChecked, AService, [])
|
providerDef(NodeFlags.AfterContentChecked, null, AService, [])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(childFlags(vd)).toEqual([NodeFlags.AfterContentChecked, NodeFlags.None]);
|
expect(childFlags(vd)).toEqual([NodeFlags.AfterContentChecked, NodeFlags.None]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should calculate childFlags for two levels', () => {
|
||||||
|
const vd = viewDef(ViewFlags.None, [
|
||||||
|
elementDef(NodeFlags.None, null, 2, 'span'), elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(NodeFlags.AfterContentChecked, null, AService, [])
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(childFlags(vd)).toEqual([
|
||||||
|
NodeFlags.AfterContentChecked, NodeFlags.AfterContentChecked, NodeFlags.None
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should calculate childFlags for one level, multiple roots', () => {
|
it('should calculate childFlags for one level, multiple roots', () => {
|
||||||
const vd = viewDef(ViewFlags.None, [
|
const vd = viewDef(ViewFlags.None, [
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.AfterContentChecked, AService, []),
|
providerDef(NodeFlags.AfterContentChecked, null, AService, []),
|
||||||
elementDef(NodeFlags.None, 2, 'span'),
|
elementDef(NodeFlags.None, null, 2, 'span'),
|
||||||
providerDef(NodeFlags.AfterContentInit, AService, []),
|
providerDef(NodeFlags.AfterContentInit, null, AService, []),
|
||||||
providerDef(NodeFlags.AfterViewChecked, AService, []),
|
providerDef(NodeFlags.AfterViewChecked, null, AService, []),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(childFlags(vd)).toEqual([
|
expect(childFlags(vd)).toEqual([
|
||||||
|
@ -147,12 +158,12 @@ export function main() {
|
||||||
|
|
||||||
it('should calculate childFlags for multiple levels', () => {
|
it('should calculate childFlags for multiple levels', () => {
|
||||||
const vd = viewDef(ViewFlags.None, [
|
const vd = viewDef(ViewFlags.None, [
|
||||||
elementDef(NodeFlags.None, 2, 'span'),
|
elementDef(NodeFlags.None, null, 2, 'span'),
|
||||||
elementDef(NodeFlags.None, 1, 'span'),
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
providerDef(NodeFlags.AfterContentChecked, AService, []),
|
providerDef(NodeFlags.AfterContentChecked, null, AService, []),
|
||||||
elementDef(NodeFlags.None, 2, 'span'),
|
elementDef(NodeFlags.None, null, 2, 'span'),
|
||||||
providerDef(NodeFlags.AfterContentInit, AService, []),
|
providerDef(NodeFlags.AfterContentInit, null, AService, []),
|
||||||
providerDef(NodeFlags.AfterViewInit, AService, []),
|
providerDef(NodeFlags.AfterViewInit, null, AService, []),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(childFlags(vd)).toEqual([
|
expect(childFlags(vd)).toEqual([
|
||||||
|
@ -161,6 +172,71 @@ export function main() {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('childMatchedQueries', () => {
|
||||||
|
function childMatchedQueries(viewDef: ViewDefinition): string[][] {
|
||||||
|
return viewDef.nodes.map(node => Object.keys(node.childMatchedQueries).sort());
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should calculate childMatchedQueries for one level', () => {
|
||||||
|
const vd = viewDef(ViewFlags.None, [
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(NodeFlags.None, [['q1', QueryValueType.Provider]], AService, [])
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(childMatchedQueries(vd)).toEqual([['q1'], []]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate childMatchedQueries for two levels', () => {
|
||||||
|
const vd = viewDef(ViewFlags.None, [
|
||||||
|
elementDef(NodeFlags.None, null, 2, 'span'), elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(NodeFlags.None, [['q1', QueryValueType.Provider]], AService, [])
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(childMatchedQueries(vd)).toEqual([['q1'], ['q1'], []]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate childMatchedQueries for one level, multiple roots', () => {
|
||||||
|
const vd = viewDef(ViewFlags.None, [
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(NodeFlags.None, [['q1', QueryValueType.Provider]], AService, []),
|
||||||
|
elementDef(NodeFlags.None, null, 2, 'span'),
|
||||||
|
providerDef(NodeFlags.None, [['q2', QueryValueType.Provider]], AService, []),
|
||||||
|
providerDef(NodeFlags.None, [['q3', QueryValueType.Provider]], AService, []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(childMatchedQueries(vd)).toEqual([['q1'], [], ['q2', 'q3'], [], []]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate childMatchedQueries for multiple levels', () => {
|
||||||
|
const vd = viewDef(ViewFlags.None, [
|
||||||
|
elementDef(NodeFlags.None, null, 2, 'span'),
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
providerDef(NodeFlags.None, [['q1', QueryValueType.Provider]], AService, []),
|
||||||
|
elementDef(NodeFlags.None, null, 2, 'span'),
|
||||||
|
providerDef(NodeFlags.None, [['q2', QueryValueType.Provider]], AService, []),
|
||||||
|
providerDef(NodeFlags.None, [['q3', QueryValueType.Provider]], AService, []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(childMatchedQueries(vd)).toEqual([['q1'], ['q1'], [], ['q2', 'q3'], [], []]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should included embedded views into childMatchedQueries', () => {
|
||||||
|
const vd = viewDef(ViewFlags.None, [
|
||||||
|
elementDef(NodeFlags.None, null, 1, 'span'),
|
||||||
|
anchorDef(
|
||||||
|
NodeFlags.None, null, 0,
|
||||||
|
viewDef(
|
||||||
|
ViewFlags.None,
|
||||||
|
[
|
||||||
|
elementDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 1, 'span'),
|
||||||
|
]))
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Note: the template will become a sibling to the anchor once stamped out,
|
||||||
|
expect(childMatchedQueries(vd)).toEqual([['q1'], []]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,16 +24,17 @@ export class TreeComponent {
|
||||||
let viewFlags = ViewFlags.DirectDom;
|
let viewFlags = ViewFlags.DirectDom;
|
||||||
|
|
||||||
const TreeComponent_Host: ViewDefinition = viewDef(viewFlags, [
|
const TreeComponent_Host: ViewDefinition = viewDef(viewFlags, [
|
||||||
elementDef(NodeFlags.None, 1, 'tree'),
|
elementDef(NodeFlags.None, null, 1, 'tree'),
|
||||||
providerDef(NodeFlags.None, TreeComponent, [], null, null, () => TreeComponent_0),
|
providerDef(NodeFlags.None, null, TreeComponent, [], null, null, null, () => TreeComponent_0),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const TreeComponent_1: ViewDefinition = viewDef(
|
const TreeComponent_1: ViewDefinition = viewDef(
|
||||||
viewFlags,
|
viewFlags,
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, 1, 'tree'),
|
elementDef(NodeFlags.None, null, 1, 'tree'),
|
||||||
providerDef(
|
providerDef(
|
||||||
NodeFlags.None, TreeComponent, [], {data: [0, 'data']}, null, () => TreeComponent_0),
|
NodeFlags.None, null, TreeComponent, [], {data: [0, 'data']}, null, null,
|
||||||
|
() => TreeComponent_0),
|
||||||
],
|
],
|
||||||
(updater: NodeUpdater, view: ViewData) => {
|
(updater: NodeUpdater, view: ViewData) => {
|
||||||
const cmp = view.component;
|
const cmp = view.component;
|
||||||
|
@ -43,9 +44,10 @@ const TreeComponent_1: ViewDefinition = viewDef(
|
||||||
const TreeComponent_2: ViewDefinition = viewDef(
|
const TreeComponent_2: ViewDefinition = viewDef(
|
||||||
viewFlags,
|
viewFlags,
|
||||||
[
|
[
|
||||||
elementDef(NodeFlags.None, 1, 'tree'),
|
elementDef(NodeFlags.None, null, 1, 'tree'),
|
||||||
providerDef(
|
providerDef(
|
||||||
NodeFlags.None, TreeComponent, [], {data: [0, 'data']}, null, () => TreeComponent_0),
|
NodeFlags.None, null, TreeComponent, [], {data: [0, 'data']}, null, null,
|
||||||
|
() => TreeComponent_0),
|
||||||
],
|
],
|
||||||
(updater: NodeUpdater, view: ViewData) => {
|
(updater: NodeUpdater, view: ViewData) => {
|
||||||
const cmp = view.component;
|
const cmp = view.component;
|
||||||
|
@ -56,12 +58,13 @@ const TreeComponent_0: ViewDefinition = viewDef(
|
||||||
viewFlags,
|
viewFlags,
|
||||||
[
|
[
|
||||||
elementDef(
|
elementDef(
|
||||||
NodeFlags.None, 1, 'span', null, [[BindingType.ElementStyle, 'backgroundColor', null]]),
|
NodeFlags.None, null, 1, 'span', null,
|
||||||
|
[[BindingType.ElementStyle, 'backgroundColor', null]]),
|
||||||
textDef([' ', ' ']),
|
textDef([' ', ' ']),
|
||||||
anchorDef(NodeFlags.HasEmbeddedViews, 1, TreeComponent_1),
|
anchorDef(NodeFlags.HasEmbeddedViews, null, 1, TreeComponent_1),
|
||||||
providerDef(NodeFlags.None, NgIf, [ViewContainerRef, TemplateRef], {ngIf: [0, 'ngIf']}),
|
providerDef(NodeFlags.None, null, NgIf, [ViewContainerRef, TemplateRef], {ngIf: [0, 'ngIf']}),
|
||||||
anchorDef(NodeFlags.HasEmbeddedViews, 1, TreeComponent_2),
|
anchorDef(NodeFlags.HasEmbeddedViews, null, 1, TreeComponent_2),
|
||||||
providerDef(NodeFlags.None, NgIf, [ViewContainerRef, TemplateRef], {ngIf: [0, 'ngIf']}),
|
providerDef(NodeFlags.None, null, NgIf, [ViewContainerRef, TemplateRef], {ngIf: [0, 'ngIf']}),
|
||||||
],
|
],
|
||||||
(updater: NodeUpdater, view: ViewData) => {
|
(updater: NodeUpdater, view: ViewData) => {
|
||||||
const cmp = view.component;
|
const cmp = view.component;
|
||||||
|
|
Loading…
Reference in New Issue