feat(core): add query support to view engine
Part of #14013 closes #14084
This commit is contained in:

committed by
Victor Berchet

parent
fc8694ed11
commit
1e729d7ba2
@ -8,11 +8,16 @@
|
||||
|
||||
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';
|
||||
|
||||
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 {
|
||||
type: NodeType.Element,
|
||||
// will bet set by the view definition
|
||||
@ -20,12 +25,13 @@ export function anchorDef(
|
||||
reverseChildIndex: undefined,
|
||||
parent: undefined,
|
||||
childFlags: undefined,
|
||||
childMatchedQueries: undefined,
|
||||
bindingIndex: undefined,
|
||||
disposableIndex: undefined,
|
||||
providerIndices: undefined,
|
||||
// regular values
|
||||
flags,
|
||||
childCount,
|
||||
matchedQueries: matchedQueryDefs, childCount,
|
||||
bindings: [],
|
||||
disposableCount: 0,
|
||||
element: {name: undefined, attrs: undefined, outputs: [], template},
|
||||
@ -36,11 +42,16 @@ export function anchorDef(
|
||||
}
|
||||
|
||||
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?:
|
||||
([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] |
|
||||
[BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[],
|
||||
outputs?: (string | [string, string])[]): NodeDef {
|
||||
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
|
||||
if (matchedQueries) {
|
||||
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
||||
}
|
||||
bindings = bindings || [];
|
||||
const bindingDefs = new Array(bindings.length);
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
@ -81,12 +92,13 @@ export function elementDef(
|
||||
reverseChildIndex: undefined,
|
||||
parent: undefined,
|
||||
childFlags: undefined,
|
||||
childMatchedQueries: undefined,
|
||||
bindingIndex: undefined,
|
||||
disposableIndex: undefined,
|
||||
providerIndices: undefined,
|
||||
// regular values
|
||||
flags,
|
||||
childCount,
|
||||
matchedQueries: matchedQueryDefs, childCount,
|
||||
bindings: bindingDefs,
|
||||
disposableCount: outputDefs.length,
|
||||
element: {name, attrs: fixedAttrs, outputs: outputDefs, template: undefined},
|
||||
@ -150,8 +162,11 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): No
|
||||
}
|
||||
}
|
||||
return {
|
||||
elementOrText:
|
||||
{node: el, embeddedViews: (def.flags & NodeFlags.HasEmbeddedViews) ? [] : undefined},
|
||||
elementOrText: {
|
||||
node: el,
|
||||
embeddedViews: (def.flags & NodeFlags.HasEmbeddedViews) ? [] : undefined,
|
||||
projectedViews: undefined
|
||||
},
|
||||
provider: undefined,
|
||||
pureExpression: undefined,
|
||||
};
|
||||
|
@ -10,12 +10,14 @@ import {SimpleChange, SimpleChanges} from '../change_detection/change_detection'
|
||||
import {Injector} from '../di';
|
||||
import {stringify} from '../facade/lang';
|
||||
import {ElementRef} from '../linker/element_ref';
|
||||
import {ExpressionChangedAfterItHasBeenCheckedError} from '../linker/errors';
|
||||
import {QueryList} from '../linker/query_list';
|
||||
import {TemplateRef} from '../linker/template_ref';
|
||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||
import {Renderer} from '../render/api';
|
||||
|
||||
import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderOutputDef, Services, ViewData, ViewDefinition, ViewFlags} from './types';
|
||||
import {checkAndUpdateBinding, checkAndUpdateBindingWithChange, setBindingDebugInfo} from './util';
|
||||
import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderOutputDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, ViewDefinition, ViewFlags} from './types';
|
||||
import {checkAndUpdateBinding, checkAndUpdateBindingWithChange, declaredViewContainer, setBindingDebugInfo} from './util';
|
||||
|
||||
const _tokenKeyCache = new Map<any, string>();
|
||||
|
||||
@ -25,9 +27,16 @@ const ViewContainerRefTokenKey = tokenKey(ViewContainerRef);
|
||||
const TemplateRefTokenKey = tokenKey(TemplateRef);
|
||||
|
||||
export function providerDef(
|
||||
flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[],
|
||||
props?: {[name: string]: [number, string]}, outputs?: {[name: string]: string},
|
||||
component?: () => ViewDefinition): NodeDef {
|
||||
flags: NodeFlags, matchedQueries: [string, QueryValueType][], ctor: any,
|
||||
deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
|
||||
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[] = [];
|
||||
if (props) {
|
||||
for (let prop in props) {
|
||||
@ -57,9 +66,27 @@ export function providerDef(
|
||||
}
|
||||
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) {
|
||||
flags = flags | NodeFlags.HasComponent;
|
||||
}
|
||||
if (contentQueryDefs.length) {
|
||||
flags = flags | NodeFlags.HasContentQuery;
|
||||
}
|
||||
if (viewQueryDefs.length) {
|
||||
flags = flags | NodeFlags.HasViewQuery;
|
||||
}
|
||||
|
||||
return {
|
||||
type: NodeType.Provider,
|
||||
// will bet set by the view definition
|
||||
@ -67,17 +94,26 @@ export function providerDef(
|
||||
reverseChildIndex: undefined,
|
||||
parent: undefined,
|
||||
childFlags: undefined,
|
||||
childMatchedQueries: undefined,
|
||||
bindingIndex: undefined,
|
||||
disposableIndex: undefined,
|
||||
providerIndices: undefined,
|
||||
// regular values
|
||||
flags,
|
||||
matchedQueries: matchedQueryDefs,
|
||||
childCount: 0, bindings,
|
||||
disposableCount: outputDefs.length,
|
||||
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,
|
||||
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);
|
||||
}
|
||||
}
|
||||
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 {
|
||||
elementOrText: undefined,
|
||||
provider: {instance: provider, componentView: componentView},
|
||||
provider: {instance: provider, componentView: componentView, queries},
|
||||
pureExpression: undefined,
|
||||
};
|
||||
}
|
||||
@ -202,7 +250,7 @@ export function resolveDep(
|
||||
if (elDef.parent != null) {
|
||||
elIndex = elDef.parent;
|
||||
} else {
|
||||
elIndex = view.parentIndex;
|
||||
elIndex = view.parentDiIndex;
|
||||
view = view.parent;
|
||||
}
|
||||
}
|
||||
@ -228,7 +276,7 @@ export function resolveDep(
|
||||
return view.nodes[providerIndex].provider.instance;
|
||||
}
|
||||
}
|
||||
elIndex = view.parentIndex;
|
||||
elIndex = view.parentDiIndex;
|
||||
view = view.parent;
|
||||
}
|
||||
return Injector.NULL.get(depDef.token, notFoundValue);
|
||||
@ -265,6 +313,153 @@ function checkAndUpdateProp(
|
||||
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) {
|
||||
if (!(view.def.nodeFlags & lifecycles)) {
|
||||
return;
|
||||
|
@ -44,11 +44,13 @@ function _pureExpressionDef(
|
||||
reverseChildIndex: undefined,
|
||||
parent: undefined,
|
||||
childFlags: undefined,
|
||||
childMatchedQueries: undefined,
|
||||
bindingIndex: undefined,
|
||||
disposableIndex: undefined,
|
||||
providerIndices: undefined,
|
||||
// regular values
|
||||
flags: 0,
|
||||
matchedQueries: {},
|
||||
childCount: 0, bindings,
|
||||
disposableCount: 0,
|
||||
element: undefined,
|
||||
|
@ -29,11 +29,13 @@ export function textDef(constants: string[]): NodeDef {
|
||||
reverseChildIndex: undefined,
|
||||
parent: undefined,
|
||||
childFlags: undefined,
|
||||
childMatchedQueries: undefined,
|
||||
bindingIndex: undefined,
|
||||
disposableIndex: undefined,
|
||||
providerIndices: undefined,
|
||||
// regular values
|
||||
flags: 0,
|
||||
matchedQueries: {},
|
||||
childCount: 0, bindings,
|
||||
disposableCount: 0,
|
||||
element: undefined,
|
||||
@ -55,7 +57,7 @@ export function createText(view: ViewData, renderHost: any, def: NodeDef): NodeD
|
||||
}
|
||||
}
|
||||
return {
|
||||
elementOrText: {node: renderNode, embeddedViews: undefined},
|
||||
elementOrText: {node: renderNode, embeddedViews: undefined, projectedViews: undefined},
|
||||
provider: undefined,
|
||||
pureExpression: undefined
|
||||
};
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {PipeTransform} from '../change_detection/change_detection';
|
||||
import {QueryList} from '../linker/query_list';
|
||||
import {TemplateRef} from '../linker/template_ref';
|
||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||
import {RenderComponentType, Renderer, RootRenderer} from '../render/api';
|
||||
@ -36,6 +37,11 @@ export interface ViewDefinition {
|
||||
lastRootNode: number;
|
||||
bindingCount: 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;
|
||||
@ -69,11 +75,21 @@ export interface NodeDef {
|
||||
childCount: number;
|
||||
/** aggregated NodeFlags for all children **/
|
||||
childFlags: NodeFlags;
|
||||
|
||||
providerIndices: {[tokenKey: string]: number};
|
||||
bindingIndex: number;
|
||||
bindings: BindingDef[];
|
||||
disposableIndex: 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;
|
||||
provider: ProviderDef;
|
||||
text: TextDef;
|
||||
@ -84,7 +100,7 @@ export enum NodeType {
|
||||
Element,
|
||||
Text,
|
||||
Provider,
|
||||
PureExpression
|
||||
PureExpression,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,6 +118,8 @@ export enum NodeFlags {
|
||||
AfterViewChecked = 1 << 7,
|
||||
HasEmbeddedViews = 1 << 8,
|
||||
HasComponent = 1 << 9,
|
||||
HasContentQuery = 1 << 10,
|
||||
HasViewQuery = 1 << 11,
|
||||
}
|
||||
|
||||
export interface ElementDef {
|
||||
@ -140,10 +158,30 @@ export interface ProviderDef {
|
||||
ctor: any;
|
||||
deps: DepDef[];
|
||||
outputs: ProviderOutputDef[];
|
||||
contentQueries: QueryDef[];
|
||||
viewQueries: QueryDef[];
|
||||
// closure to allow recursive components
|
||||
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 PureExpressionDef {
|
||||
@ -190,6 +228,10 @@ export interface ViewData {
|
||||
// index of parent element / anchor. Not the index
|
||||
// of the provider with the component view.
|
||||
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;
|
||||
component: any;
|
||||
context: any;
|
||||
@ -214,11 +256,17 @@ export interface NodeData {
|
||||
export interface ElementOrTextData {
|
||||
node: any;
|
||||
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 {
|
||||
instance: any;
|
||||
componentView: ViewData;
|
||||
queries: {[queryId: string]: QueryList<any>};
|
||||
}
|
||||
|
||||
export interface PureExpressionData {
|
||||
|
@ -60,3 +60,11 @@ export function checkAndUpdateBindingWithChange(
|
||||
}
|
||||
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 {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 {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
|
||||
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 viewDisposableCount = 0;
|
||||
let viewFlags = 0;
|
||||
let viewMatchedQueries: {[queryId: string]: boolean} = {};
|
||||
let currentParent: NodeDef = null;
|
||||
let lastRootNode: NodeDef = null;
|
||||
for (let i = 0; i < nodesWithoutIndices.length; i++) {
|
||||
@ -37,6 +38,7 @@ export function viewDef(
|
||||
const newParent = nodes[currentParent.parent];
|
||||
if (newParent) {
|
||||
newParent.childFlags |= currentParent.childFlags;
|
||||
copyInto(currentParent.childMatchedQueries, newParent.childMatchedQueries);
|
||||
}
|
||||
currentParent = newParent;
|
||||
}
|
||||
@ -54,10 +56,15 @@ export function viewDef(
|
||||
validateNode(currentParent, node);
|
||||
|
||||
viewFlags |= node.flags;
|
||||
copyInto(node.matchedQueries, viewMatchedQueries);
|
||||
viewBindingCount += node.bindings.length;
|
||||
viewDisposableCount += node.disposableCount;
|
||||
if (currentParent) {
|
||||
currentParent.childFlags |= node.flags;
|
||||
copyInto(node.matchedQueries, currentParent.childMatchedQueries);
|
||||
if (node.element && node.element.template) {
|
||||
copyInto(node.element.template.nodeMatchedQueries, currentParent.childMatchedQueries);
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentParent) {
|
||||
@ -65,15 +72,29 @@ export function viewDef(
|
||||
}
|
||||
if (node.provider) {
|
||||
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) {
|
||||
currentParent = node;
|
||||
}
|
||||
}
|
||||
while (currentParent) {
|
||||
const newParent = nodes[currentParent.parent];
|
||||
if (newParent) {
|
||||
newParent.childFlags |= currentParent.childFlags;
|
||||
copyInto(currentParent.childMatchedQueries, newParent.childMatchedQueries);
|
||||
}
|
||||
currentParent = newParent;
|
||||
}
|
||||
|
||||
return {
|
||||
nodeFlags: viewFlags,
|
||||
flags,
|
||||
nodeMatchedQueries: viewMatchedQueries, flags,
|
||||
nodes: nodes, reverseChildNodes,
|
||||
update: update || NOOP,
|
||||
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(
|
||||
currentParent: NodeDef, i: number, childCount: number, nodeCount: number) {
|
||||
// Notes about reverse child order:
|
||||
@ -158,9 +185,7 @@ function cloneAndModifyNode(nodeDef: NodeDef, values: {
|
||||
providerIndices: {[tokenKey: string]: number}
|
||||
}): NodeDef {
|
||||
const clonedNode: NodeDef = <any>{};
|
||||
for (let prop in nodeDef) {
|
||||
(<any>clonedNode)[prop] = (<any>nodeDef)[prop];
|
||||
}
|
||||
copyInto(nodeDef, clonedNode);
|
||||
|
||||
clonedNode.index = values.index;
|
||||
clonedNode.bindingIndex = values.bindingIndex;
|
||||
@ -170,25 +195,28 @@ function cloneAndModifyNode(nodeDef: NodeDef, values: {
|
||||
clonedNode.providerIndices = values.providerIndices;
|
||||
// Note: We can't set the value immediately, as we need to walk the children first.
|
||||
clonedNode.childFlags = 0;
|
||||
clonedNode.childMatchedQueries = {};
|
||||
return clonedNode;
|
||||
}
|
||||
|
||||
export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
|
||||
// embedded views are seen as siblings to the anchor, so we need
|
||||
// to get the parent of the anchor and use it as parentIndex.
|
||||
const view = createView(parent.services, parent, anchorDef.parent, anchorDef.element.template);
|
||||
const view = createView(
|
||||
parent.services, parent, anchorDef.index, anchorDef.parent, anchorDef.element.template);
|
||||
initView(view, null, parent.component, context);
|
||||
return view;
|
||||
}
|
||||
|
||||
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);
|
||||
return view;
|
||||
}
|
||||
|
||||
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);
|
||||
let renderer: Renderer;
|
||||
if (def.flags != null && (def.flags & ViewFlags.DirectDom)) {
|
||||
@ -201,6 +229,7 @@ function createView(
|
||||
def,
|
||||
parent,
|
||||
parentIndex,
|
||||
parentDiIndex,
|
||||
context: undefined,
|
||||
component: undefined, nodes,
|
||||
firstChange: true, renderer, services,
|
||||
@ -227,7 +256,7 @@ function initView(view: ViewData, renderHost: any, component: any, context: any)
|
||||
case NodeType.Provider:
|
||||
let componentView: ViewData;
|
||||
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);
|
||||
break;
|
||||
@ -243,7 +272,9 @@ function initView(view: ViewData, renderHost: any, component: any, context: any)
|
||||
export function checkNoChangesView(view: ViewData) {
|
||||
view.def.update(CheckNoChanges, view);
|
||||
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
|
||||
execContentQueriesAction(view, QueryAction.CheckNoChanges);
|
||||
execComponentViewsAction(view, ViewAction.CheckNoChanges);
|
||||
updateViewQueries(view, QueryAction.CheckNoChanges);
|
||||
}
|
||||
|
||||
const CheckNoChanges: NodeUpdater = {
|
||||
@ -293,10 +324,12 @@ const CheckNoChanges: NodeUpdater = {
|
||||
export function checkAndUpdateView(view: ViewData) {
|
||||
view.def.update(CheckAndUpdate, view);
|
||||
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
|
||||
execContentQueriesAction(view, QueryAction.CheckAndUpdate);
|
||||
|
||||
callLifecycleHooksChildrenFirst(
|
||||
view, NodeFlags.AfterContentChecked | (view.firstChange ? NodeFlags.AfterContentInit : 0));
|
||||
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
|
||||
updateViewQueries(view, QueryAction.CheckAndUpdate);
|
||||
|
||||
callLifecycleHooksChildrenFirst(
|
||||
view, NodeFlags.AfterViewChecked | (view.firstChange ? NodeFlags.AfterViewInit : 0));
|
||||
|
@ -7,18 +7,28 @@
|
||||
*/
|
||||
|
||||
import {NodeData, NodeFlags, ViewData} from './types';
|
||||
import {declaredViewContainer} from './util';
|
||||
|
||||
export function attachEmbeddedView(node: NodeData, viewIndex: number, view: ViewData) {
|
||||
let embeddedViews = node.elementOrText.embeddedViews;
|
||||
if (viewIndex == null) {
|
||||
viewIndex = embeddedViews.length;
|
||||
}
|
||||
// perf: array.push is faster than array.splice!
|
||||
if (viewIndex >= embeddedViews.length) {
|
||||
embeddedViews.push(view);
|
||||
} else {
|
||||
embeddedViews.splice(viewIndex, 0, view);
|
||||
addToArray(embeddedViews, viewIndex, view);
|
||||
const dvc = declaredViewContainer(view);
|
||||
if (dvc && dvc !== node) {
|
||||
let projectedViews = dvc.elementOrText.projectedViews;
|
||||
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 prevNode = prevView ? prevView.nodes[prevView.def.lastRootNode] : node;
|
||||
const prevRenderNode = prevNode.elementOrText.node;
|
||||
@ -41,12 +51,19 @@ export function detachEmbeddedView(node: NodeData, viewIndex: number): ViewData
|
||||
viewIndex = embeddedViews.length;
|
||||
}
|
||||
const view = embeddedViews[viewIndex];
|
||||
// perf: array.pop is faster than array.splice!
|
||||
if (viewIndex >= embeddedViews.length - 1) {
|
||||
embeddedViews.pop();
|
||||
} else {
|
||||
embeddedViews.splice(viewIndex, 1);
|
||||
removeFromArray(embeddedViews, viewIndex);
|
||||
|
||||
const dvc = declaredViewContainer(view);
|
||||
if (dvc && dvc !== node) {
|
||||
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) {
|
||||
view.renderer.detachView(rootRenderNodes(view));
|
||||
} else {
|
||||
@ -59,6 +76,45 @@ export function detachEmbeddedView(node: NodeData, viewIndex: number): ViewData
|
||||
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[] {
|
||||
const renderNodes: any[] = [];
|
||||
collectSiblingRenderNodes(view, 0, renderNodes);
|
||||
|
Reference in New Issue
Block a user