feat(compiler): integrate compiler with view engine (#14487)

Aspects: di, query, content projection

Included refactoring:
- use a number as query id
- use a bloom filter for aggregating matched queries of nested elements
- separate static vs dynamic queries

Part of #14013
This commit is contained in:
Tobias Bosch
2017-02-15 08:36:49 -08:00
committed by Igor Minar
parent e9ba7aa4f8
commit 4e7752a12a
29 changed files with 810 additions and 558 deletions
@@ -304,6 +304,7 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
providers: CompileProviderMetadata[];
viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[];
viewQueries: CompileQueryMetadata[];
entryComponents: CompileEntryComponentMetadata[];
changeDetection: ChangeDetectionStrategy;
template: CompileTemplateSummary;
@@ -480,6 +481,7 @@ export class CompileDirectiveMetadata {
providers: this.providers,
viewProviders: this.viewProviders,
queries: this.queries,
viewQueries: this.viewQueries,
entryComponents: this.entryComponents,
changeDetection: this.changeDetection,
template: this.template && this.template.toSummary(),
@@ -368,6 +368,12 @@ export class Identifiers {
{name: 'ɵviewEngine', moduleUrl: CORE, member: 'pipeDef', runtime: ɵviewEngine.pipeDef};
static nodeValue: IdentifierSpec =
{name: 'ɵviewEngine', moduleUrl: CORE, member: 'nodeValue', runtime: ɵviewEngine.nodeValue};
static ngContentDef: IdentifierSpec = {
name: 'ɵviewEngine',
moduleUrl: CORE,
member: 'ngContentDef',
runtime: ɵviewEngine.ngContentDef
};
static unwrapValue: IdentifierSpec = {
name: 'ɵviewEngine',
moduleUrl: CORE,
@@ -11,7 +11,7 @@ import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveS
import {isBlank, isPresent} from './facade/lang';
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
import {ParseError, ParseSourceSpan} from './parse_util';
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, QueryId, QueryMatch, ReferenceAst} from './template_parser/template_ast';
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst} from './template_parser/template_ast';
export class ProviderError extends ParseError {
constructor(message: string, span: ParseSourceSpan) { super(span, message); }
@@ -19,7 +19,7 @@ export class ProviderError extends ParseError {
export interface QueryWithId {
meta: CompileQueryMetadata;
id: QueryId;
queryId: number;
}
export class ProviderViewContext {
@@ -57,16 +57,21 @@ export class ProviderElementContext {
constructor(
public viewContext: ProviderViewContext, private _parent: ProviderElementContext,
private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[],
refs: ReferenceAst[], private _sourceSpan: ParseSourceSpan) {
refs: ReferenceAst[], isTemplate: boolean, contentQueryStartId: number,
private _sourceSpan: ParseSourceSpan) {
this._attrs = {};
attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value);
const directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive);
this._allProviders =
_resolveProvidersFromDirectives(directivesMeta, _sourceSpan, viewContext.errors);
this._contentQueries = _getContentQueries(this.depth, directivesMeta);
this._contentQueries = _getContentQueries(contentQueryStartId, directivesMeta);
Array.from(this._allProviders.values()).forEach((provider) => {
this._addQueryReadsTo(provider.token, provider.token, this._queriedTokens);
});
if (isTemplate) {
const templateRefId = createIdentifierToken(Identifiers.TemplateRef);
this._addQueryReadsTo(templateRefId, templateRefId, this._queriedTokens);
}
refs.forEach((refAst) => {
let defaultQueryValue = refAst.value || createIdentifierToken(Identifiers.ElementRef);
this._addQueryReadsTo({value: refAst.name}, defaultQueryValue, this._queriedTokens);
@@ -91,16 +96,6 @@ export class ProviderElementContext {
});
}
get depth(): number {
let d = 0;
let current: ProviderElementContext = this;
while (current._parent) {
d++;
current = current._parent;
}
return d;
}
get transformProviders(): ProviderAst[] {
return Array.from(this._transformedProviders.values());
}
@@ -133,7 +128,7 @@ export class ProviderElementContext {
queryMatches = [];
queryReadTokens.set(tokenRef, queryMatches);
}
queryMatches.push({query: query.id, value: queryValue});
queryMatches.push({queryId: query.queryId, value: queryValue});
});
}
@@ -483,24 +478,24 @@ function _resolveProviders(
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, QueryWithId[]> {
// Note: queries start with id 1 so we can use the number in a Bloom filter!
let viewQueryId = 1;
const viewQueries = new Map<any, QueryWithId[]>();
if (component.viewQueries) {
component.viewQueries.forEach(
(query, queryIndex) => _addQueryToTokenMap(
viewQueries,
{meta: query, id: {elementDepth: null, directiveIndex: null, queryIndex: queryIndex}}));
(query) => _addQueryToTokenMap(viewQueries, {meta: query, queryId: viewQueryId++}));
}
return viewQueries;
}
function _getContentQueries(
elementDepth: number, directives: CompileDirectiveSummary[]): Map<any, QueryWithId[]> {
contentQueryStartId: number, directives: CompileDirectiveSummary[]): Map<any, QueryWithId[]> {
let contentQueryId = contentQueryStartId;
const contentQueries = new Map<any, QueryWithId[]>();
directives.forEach((directive, directiveIndex) => {
if (directive.queries) {
directive.queries.forEach(
(query, queryIndex) => _addQueryToTokenMap(
contentQueries, {meta: query, id: {elementDepth, directiveIndex, queryIndex}}));
(query) => _addQueryToTokenMap(contentQueries, {meta: query, queryId: contentQueryId++}));
}
});
return contentQueries;
@@ -170,7 +170,7 @@ export class DirectiveAst implements TemplateAst {
constructor(
public directive: CompileDirectiveSummary, public inputs: BoundDirectivePropertyAst[],
public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[],
public sourceSpan: ParseSourceSpan) {}
public contentQueryStartId: number, public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitDirective(this, context);
}
@@ -241,17 +241,8 @@ export enum PropertyBindingType {
Animation
}
/**
* This id differentiates a query on an element from any query on any child.
*/
export interface QueryId {
elementDepth: number;
directiveIndex: number;
queryIndex: number;
}
export interface QueryMatch {
query: QueryId;
queryId: number;
value: CompileTokenMetadata;
}
@@ -207,12 +207,15 @@ export class TemplateParser {
class TemplateParseVisitor implements html.Visitor {
selectorMatcher = new SelectorMatcher();
directivesIndex = new Map<CompileDirectiveSummary, number>();
ngContentCount: number = 0;
ngContentCount = 0;
contentQueryStartId: number;
constructor(
public providerViewContext: ProviderViewContext, directives: CompileDirectiveSummary[],
private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry,
private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) {
// Note: queries start with id 1 so we can use the number in a Bloom filter!
this.contentQueryStartId = providerViewContext.component.viewQueries.length + 1;
directives.forEach((directive, index) => {
const selector = CssSelector.parse(directive.selector);
this.selectorMatcher.addSelectables(selector, directive);
@@ -241,6 +244,7 @@ class TemplateParseVisitor implements html.Visitor {
visitComment(comment: html.Comment, context: any): any { return null; }
visitElement(element: html.Element, parent: ElementContext): any {
const queryStartIndex = this.contentQueryStartId;
const nodeName = element.name;
const preparsedElement = preparseElement(element);
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
@@ -322,9 +326,9 @@ class TemplateParseVisitor implements html.Visitor {
const providerContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
references, element.sourceSpan);
references, isTemplateElement, queryStartIndex, element.sourceSpan);
const children = html.visitAll(
const children: TemplateAst[] = html.visitAll(
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
ElementContext.create(
isTemplateElement, directiveAsts,
@@ -378,6 +382,7 @@ class TemplateParseVisitor implements html.Visitor {
}
if (hasInlineTemplates) {
const templateQueryStartIndex = this.contentQueryStartId;
const templateCssSelector =
createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
const {directives: templateDirectiveMetas} =
@@ -392,7 +397,7 @@ class TemplateParseVisitor implements html.Visitor {
templateDirectiveAsts, templateElementProps, element.sourceSpan);
const templateProviderContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, parent.isTemplateElement,
templateDirectiveAsts, [], [], element.sourceSpan);
templateDirectiveAsts, [], [], true, templateQueryStartIndex, element.sourceSpan);
templateProviderContext.afterElement();
parsedElement = new EmbeddedTemplateAst(
@@ -585,8 +590,11 @@ class TemplateParseVisitor implements html.Visitor {
matchedReferences.add(elOrDirRef.name);
}
});
const contentQueryStartId = this.contentQueryStartId;
this.contentQueryStartId += directive.queries.length;
return new DirectiveAst(
directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
directive, directiveProperties, hostProperties, hostEvents, contentQueryStartId,
sourceSpan);
});
elementOrDirectiveRefs.forEach((elOrDirRef) => {
@@ -779,7 +787,7 @@ class NonBindableVisitor implements html.Visitor {
const attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]);
const selector = createElementCssSelector(ast.name, attrNameAndValues);
const ngContentIndex = parent.findNgContentIndex(selector);
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
const children: TemplateAst[] = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
return new ElementAst(
ast.name, html.visitAll(this, ast.attrs), [], [], [], [], [], false, [], children,
ngContentIndex, ast.sourceSpan, ast.endSourceSpan);
File diff suppressed because it is too large Load Diff
@@ -155,7 +155,7 @@ export function main() {
expectVisitedNode(
new class extends
NullVisitor{visitDirective(ast: DirectiveAst, context: any): any{return ast;}},
new DirectiveAst(null, [], [], [], null));
new DirectiveAst(null, [], [], [], 0, null));
});
it('should visit DirectiveAst', () => {
@@ -178,7 +178,7 @@ export function main() {
new BoundEventAst('foo', 'bar', 'goo', null, null),
new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null),
new AttrAst('foo', 'bar', null), new BoundTextAst(null, 0, null),
new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], null),
new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], 0, null),
new BoundDirectivePropertyAst('foo', 'bar', null, null)
];
const result = templateVisitAll(visitor, nodes, null);
+26 -27
View File
@@ -10,39 +10,39 @@ import {isDevMode} from '../application_ref';
import {SecurityContext} from '../security';
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData} from './types';
import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, resolveViewDefinition, sliceErrorStack} from './util';
import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, sliceErrorStack, splitMatchedQueriesDsl} from './util';
export function anchorDef(
flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef {
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
if (matchedQueries) {
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
}
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
ngContentIndex: number, childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef {
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
// skip the call to sliceErrorStack itself + the call to this function.
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
const template = templateFactory ? resolveViewDefinition(templateFactory) : null;
return {
type: NodeType.Element,
// will bet set by the view definition
index: undefined,
reverseChildIndex: undefined,
parent: undefined,
childFlags: undefined,
childMatchedQueries: undefined,
renderParent: undefined,
bindingIndex: undefined,
disposableIndex: undefined,
// regular values
flags,
matchedQueries: matchedQueryDefs, ngContentIndex, childCount,
childFlags: 0,
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount,
bindings: [],
disposableCount: 0,
element: {
name: undefined,
attrs: undefined,
outputs: [], template,
outputs: [], template, source,
// will bet set by the view definition
providerIndices: undefined, source,
component: undefined,
publicProviders: undefined,
allProviders: undefined,
},
provider: undefined,
text: undefined,
@@ -53,18 +53,16 @@ export function anchorDef(
}
export function elementDef(
flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
ngContentIndex: number, 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 {
// skip the call to sliceErrorStack itself + the call to this function.
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
if (matchedQueries) {
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
}
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
bindings = bindings || [];
const bindingDefs: BindingDef[] = new Array(bindings.length);
for (let i = 0; i < bindings.length; i++) {
@@ -104,22 +102,24 @@ export function elementDef(
index: undefined,
reverseChildIndex: undefined,
parent: undefined,
childFlags: undefined,
childMatchedQueries: undefined,
renderParent: undefined,
bindingIndex: undefined,
disposableIndex: undefined,
// regular values
flags,
matchedQueries: matchedQueryDefs, ngContentIndex, childCount,
childFlags: 0,
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount,
bindings: bindingDefs,
disposableCount: outputDefs.length,
element: {
name,
attrs: fixedAttrs,
outputs: outputDefs,
outputs: outputDefs, source,
template: undefined,
// will bet set by the view definition
providerIndices: undefined, source,
component: undefined,
publicProviders: undefined,
allProviders: undefined,
},
provider: undefined,
text: undefined,
@@ -135,8 +135,6 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El
const renderer = view.root.renderer;
let el: any;
if (view.parent || !rootSelectorOrNode) {
const parentNode =
def.parent != null ? asElementData(view, def.parent).renderElement : renderHost;
if (elDef.name) {
// TODO(vicb): move the namespace to the node definition
const nsAndName = splitNamespace(elDef.name);
@@ -144,8 +142,9 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El
} else {
el = renderer.createComment('');
}
if (parentNode) {
renderer.appendChild(parentNode, el);
const parentEl = getParentRenderElement(view, renderHost, def);
if (parentEl) {
renderer.appendChild(parentEl, el);
}
} else {
el = renderer.selectRootElement(rootSelectorOrNode);
+8 -9
View File
@@ -7,7 +7,7 @@
*/
import {NodeDef, NodeType, ViewData, asElementData} from './types';
import {RenderNodeAction, visitProjectedRenderNodes} from './util';
import {RenderNodeAction, getParentRenderElement, visitProjectedRenderNodes} from './util';
export function ngContentDef(ngContentIndex: number, index: number): NodeDef {
return {
@@ -16,13 +16,16 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef {
index: undefined,
reverseChildIndex: undefined,
parent: undefined,
childFlags: undefined,
childMatchedQueries: undefined,
renderParent: undefined,
bindingIndex: undefined,
disposableIndex: undefined,
// regular values
flags: 0,
matchedQueries: {}, ngContentIndex,
childFlags: 0,
childMatchedQueries: 0,
matchedQueries: {},
matchedQueryIds: 0,
references: {}, ngContentIndex,
childCount: 0,
bindings: [],
disposableCount: 0,
@@ -36,11 +39,7 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef {
}
export function appendNgContent(view: ViewData, renderHost: any, def: NodeDef) {
if (def.ngContentIndex != null) {
// Do nothing if we are reprojected!
return;
}
const parentEl = def.parent != null ? asElementData(view, def.parent).renderElement : renderHost;
const parentEl = getParentRenderElement(view, renderHost, def);
if (!parentEl) {
// Nothing to do if there is no parent element.
return;
File diff suppressed because it is too large Load Diff
@@ -40,13 +40,16 @@ function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]):
index: undefined,
reverseChildIndex: undefined,
parent: undefined,
childFlags: undefined,
childMatchedQueries: undefined,
renderParent: undefined,
bindingIndex: undefined,
disposableIndex: undefined,
// regular values
flags: 0,
childFlags: 0,
childMatchedQueries: 0,
matchedQueries: {},
matchedQueryIds: 0,
references: {},
ngContentIndex: undefined,
childCount: 0, bindings,
disposableCount: 0,
+68 -47
View File
@@ -13,10 +13,10 @@ import {ViewContainerRef} from '../linker/view_container_ref';
import {createTemplateRef, createViewContainerRef} from './refs';
import {NodeDef, NodeFlags, NodeType, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, asElementData, asProviderData, asQueryList} from './types';
import {declaredViewContainer, viewParentElIndex} from './util';
import {declaredViewContainer, filterQueryId, isEmbeddedView, viewParentEl} from './util';
export function queryDef(
flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef {
flags: NodeFlags, id: number, bindings: {[propName: string]: QueryBindingType}): NodeDef {
let bindingDefs: QueryBindingDef[] = [];
for (let propName in bindings) {
const bindingType = bindings[propName];
@@ -29,14 +29,17 @@ export function queryDef(
index: undefined,
reverseChildIndex: undefined,
parent: undefined,
childFlags: undefined,
childMatchedQueries: undefined,
renderParent: undefined,
bindingIndex: undefined,
disposableIndex: undefined,
// regular values
flags,
childFlags: 0,
childMatchedQueries: 0,
ngContentIndex: undefined,
matchedQueries: {},
matchedQueryIds: 0,
references: {},
childCount: 0,
bindings: [],
disposableCount: 0,
@@ -44,7 +47,7 @@ export function queryDef(
provider: undefined,
text: undefined,
pureExpression: undefined,
query: {id, bindings: bindingDefs},
query: {id, filterId: filterQueryId(id), bindings: bindingDefs},
ngContent: undefined
};
}
@@ -53,26 +56,40 @@ export function createQuery(): QueryList<any> {
return new QueryList();
}
export function dirtyParentQuery(queryId: string, view: ViewData) {
let elIndex = viewParentElIndex(view);
view = view.parent;
let queryIdx: number;
while (view) {
if (elIndex != null) {
const elementDef = view.def.nodes[elIndex];
queryIdx = elementDef.element.providerIndices[queryId];
if (queryIdx != null) {
break;
export function dirtyParentQueries(view: ViewData) {
const queryIds = view.def.nodeMatchedQueries;
while (view.parent && isEmbeddedView(view)) {
let tplDef = view.parentNodeDef;
view = view.parent;
// content queries
const end = tplDef.index + tplDef.childCount;
for (let i = 0; i <= end; i++) {
const nodeDef = view.def.nodes[i];
if ((nodeDef.flags & NodeFlags.HasContentQuery) &&
(nodeDef.flags & NodeFlags.HasDynamicQuery) &&
(nodeDef.query.filterId & queryIds) === nodeDef.query.filterId) {
asQueryList(view, i).setDirty();
}
if ((nodeDef.type === NodeType.Element && i + nodeDef.childCount < tplDef.index) ||
!(nodeDef.childFlags & NodeFlags.HasContentQuery) ||
!(nodeDef.childFlags & NodeFlags.HasDynamicQuery)) {
// skip elements that don't contain the template element or no query.
i += nodeDef.childCount;
}
}
elIndex = viewParentElIndex(view);
view = view.parent;
}
if (!view) {
throw new Error(
`Illegal State: Tried to dirty parent query ${queryId} but the query could not be found!`);
// view queries
let compDef = view.parentNodeDef;
view = view.parent;
if (view) {
for (let i = compDef.index + 1; i <= compDef.index + compDef.childCount; i++) {
const nodeDef = view.def.nodes[i];
if ((nodeDef.flags & NodeFlags.HasViewQuery) && (nodeDef.flags & NodeFlags.HasDynamicQuery)) {
asQueryList(view, i).setDirty();
}
}
}
asQueryList(view, queryIdx).setDirty();
}
export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) {
@@ -80,76 +97,80 @@ export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) {
if (!queryList.dirty) {
return;
}
const queryId = nodeDef.query.id;
const providerDef = view.def.nodes[nodeDef.parent];
const providerDef = nodeDef.parent;
const providerData = asProviderData(view, providerDef.index);
let newValues: any[];
if (nodeDef.flags & NodeFlags.HasContentQuery) {
const elementDef = view.def.nodes[providerDef.parent];
const elementDef = providerDef.parent;
newValues = calcQueryValues(
view, elementDef.index, elementDef.index + elementDef.childCount, queryId, []);
view, elementDef.index, elementDef.index + elementDef.childCount, nodeDef.query, []);
} else if (nodeDef.flags & NodeFlags.HasViewQuery) {
const compView = providerData.componentView;
newValues = calcQueryValues(compView, 0, compView.def.nodes.length - 1, queryId, []);
newValues = calcQueryValues(compView, 0, compView.def.nodes.length - 1, nodeDef.query, []);
}
queryList.reset(newValues);
let boundValue: any;
const bindings = nodeDef.query.bindings;
let notify = false;
for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i];
let boundValue: any;
switch (binding.bindingType) {
case QueryBindingType.First:
boundValue = queryList.first;
break;
case QueryBindingType.All:
boundValue = queryList;
notify = true;
break;
}
providerData.instance[binding.propName] = boundValue;
}
if (notify) {
queryList.notifyOnChanges();
}
}
function calcQueryValues(
view: ViewData, startIndex: number, endIndex: number, queryId: string, values: any[]): any[] {
const len = view.def.nodes.length;
view: ViewData, startIndex: number, endIndex: number, queryDef: QueryDef,
values: any[]): any[] {
for (let i = startIndex; i <= endIndex; i++) {
const nodeDef = view.def.nodes[i];
const value = getQueryValue(view, nodeDef, queryId);
if (value != null) {
// a match
values.push(value);
const valueType = nodeDef.matchedQueries[queryDef.id];
if (valueType != null) {
values.push(getQueryValue(view, nodeDef, valueType));
}
if (nodeDef.flags & NodeFlags.HasEmbeddedViews &&
queryId in nodeDef.element.template.nodeMatchedQueries) {
if (nodeDef.type === NodeType.Element && nodeDef.element.template &&
(nodeDef.element.template.nodeMatchedQueries & queryDef.filterId) === queryDef.filterId) {
// check embedded views that were attached at the place of their template.
const elementData = asElementData(view, i);
const embeddedViews = elementData.embeddedViews;
for (let k = 0; k < embeddedViews.length; k++) {
const embeddedView = embeddedViews[k];
const dvc = declaredViewContainer(embeddedView);
if (dvc && dvc === elementData) {
calcQueryValues(embeddedView, 0, embeddedView.def.nodes.length - 1, queryId, values);
if (embeddedViews) {
for (let k = 0; k < embeddedViews.length; k++) {
const embeddedView = embeddedViews[k];
const dvc = declaredViewContainer(embeddedView);
if (dvc && dvc === elementData) {
calcQueryValues(embeddedView, 0, embeddedView.def.nodes.length - 1, queryDef, values);
}
}
}
const projectedViews = elementData.projectedViews;
if (projectedViews) {
for (let k = 0; k < projectedViews.length; k++) {
const projectedView = projectedViews[k];
calcQueryValues(projectedView, 0, projectedView.def.nodes.length - 1, queryId, values);
calcQueryValues(projectedView, 0, projectedView.def.nodes.length - 1, queryDef, 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.
if ((nodeDef.childMatchedQueries & queryDef.filterId) !== queryDef.filterId) {
// if no child matches the query, skip the children.
i += nodeDef.childCount;
}
}
return values;
}
export function getQueryValue(view: ViewData, nodeDef: NodeDef, queryId: string): any {
const queryValueType = <QueryValueType>nodeDef.matchedQueries[queryId];
export function getQueryValue(
view: ViewData, nodeDef: NodeDef, queryValueType: QueryValueType): any {
if (queryValueType != null) {
// a match
let value: any;
@@ -164,7 +185,7 @@ export function getQueryValue(view: ViewData, nodeDef: NodeDef, queryId: string)
value = createTemplateRef(view, nodeDef);
break;
case QueryValueType.ViewContainerRef:
value = createViewContainerRef(view, nodeDef.index);
value = createViewContainerRef(view, nodeDef);
break;
case QueryValueType.Provider:
value = asProviderData(view, nodeDef.index).instance;
+24 -30
View File
@@ -16,7 +16,7 @@ import {EmbeddedViewRef, ViewRef} from '../linker/view_ref';
import {Type} from '../type';
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types';
import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentElIndex} from './util';
import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentEl} from './util';
const EMPTY_CONTEXT = new Object();
@@ -45,18 +45,7 @@ class ComponentFactory_ implements ComponentFactory<any> {
injector: Injector, projectableNodes: any[][] = null,
rootSelectorOrNode: string|any = null): ComponentRef<any> {
const viewDef = resolveViewDefinition(this._viewClass);
let componentNodeIndex: number;
const len = viewDef.nodes.length;
for (let i = 0; i < len; i++) {
const nodeDef = viewDef.nodes[i];
if (nodeDef.flags & NodeFlags.HasComponent) {
componentNodeIndex = i;
break;
}
}
if (componentNodeIndex == null) {
throw new Error(`Illegal State: Could not find a component in the view definition!`);
}
const componentNodeIndex = viewDef.nodes[0].element.component.index;
const view = Services.createRootView(
injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT);
const component = asProviderData(view, componentNodeIndex).instance;
@@ -65,9 +54,14 @@ class ComponentFactory_ implements ComponentFactory<any> {
}
class ComponentRef_ implements ComponentRef<any> {
constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) {}
get location(): ElementRef { return new ElementRef(asElementData(this._view, 0).renderElement); }
get injector(): Injector { return new Injector_(this._view, 0); }
private _elDef: NodeDef;
constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) {
this._elDef = this._view.def.nodes[0];
}
get location(): ElementRef {
return new ElementRef(asElementData(this._view, this._elDef.index).renderElement);
}
get injector(): Injector { return new Injector_(this._view, this._elDef); }
get instance(): any { return this._component; };
get hostView(): ViewRef { return this._viewRef; };
get changeDetectorRef(): ChangeDetectorRef { return this._viewRef; };
@@ -77,28 +71,28 @@ class ComponentRef_ implements ComponentRef<any> {
onDestroy(callback: Function): void { this._viewRef.onDestroy(callback); }
}
export function createViewContainerRef(view: ViewData, elIndex: number): ViewContainerRef {
return new ViewContainerRef_(view, elIndex);
export function createViewContainerRef(view: ViewData, elDef: NodeDef): ViewContainerRef {
return new ViewContainerRef_(view, elDef);
}
class ViewContainerRef_ implements ViewContainerRef {
private _data: ElementData;
constructor(private _view: ViewData, private _elIndex: number) {
this._data = asElementData(_view, _elIndex);
constructor(private _view: ViewData, private _elDef: NodeDef) {
this._data = asElementData(_view, _elDef.index);
}
get element(): ElementRef { return new ElementRef(this._data.renderElement); }
get injector(): Injector { return new Injector_(this._view, this._elIndex); }
get injector(): Injector { return new Injector_(this._view, this._elDef); }
get parentInjector(): Injector {
let view = this._view;
let elIndex = view.def.nodes[this._elIndex].parent;
while (elIndex == null && view) {
elIndex = viewParentElIndex(view);
let elDef = this._elDef.parent;
while (!elDef && view) {
elDef = viewParentEl(view);
view = view.parent;
}
return view ? new Injector_(view, elIndex) : this._view.root.injector;
return view ? new Injector_(view, elDef) : this._view.root.injector;
}
clear(): void {
@@ -200,15 +194,15 @@ class TemplateRef_ implements TemplateRef<any> {
}
}
export function createInjector(view: ViewData, elIndex: number): Injector {
return new Injector_(view, elIndex);
export function createInjector(view: ViewData, elDef: NodeDef): Injector {
return new Injector_(view, elDef);
}
class Injector_ implements Injector {
constructor(private view: ViewData, private elIndex: number) {}
constructor(private view: ViewData, private elDef: NodeDef) {}
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
return Services.resolveDep(
this.view, undefined, this.elIndex,
{flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue);
this.view, this.elDef, true, {flags: DepFlags.None, token, tokenKey: tokenKey(token)},
notFoundValue);
}
}
+23 -38
View File
@@ -16,7 +16,7 @@ import {resolveDep} from './provider';
import {getQueryValue} from './query';
import {createInjector} from './refs';
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types';
import {checkBinding, isComponentView, queryIdIsReference, renderNode, viewParentElIndex} from './util';
import {checkBinding, isComponentView, renderNode, viewParentEl} from './util';
import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view';
import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';
@@ -191,10 +191,10 @@ function debugCheckFn(
if ((binding.type === BindingType.ElementProperty ||
binding.type === BindingType.DirectiveProperty) &&
checkBinding(view, nodeDef, i, value)) {
const elIndex = nodeDef.type === NodeType.Directive ? nodeDef.parent : nodeDef.index;
const elDef = nodeDef.type === NodeType.Directive ? nodeDef.parent : nodeDef;
setBindingDebugInfo(
view.root.renderer, asElementData(view, elIndex).renderElement, binding.nonMinifiedName,
value);
view.root.renderer, asElementData(view, elDef.index).renderElement,
binding.nonMinifiedName, value);
}
}
return (<any>delegate)(view, nodeIndex, argStyle, ...givenValues);
@@ -303,49 +303,37 @@ class DebugContext_ implements DebugContext {
private nodeDef: NodeDef;
private elView: ViewData;
private elDef: NodeDef;
private compProviderIndex: number;
private compProviderDef: NodeDef;
constructor(public view: ViewData, public nodeIndex: number) {
if (nodeIndex == null) {
this.nodeIndex = 0;
}
this.nodeDef = view.def.nodes[nodeIndex];
let elIndex = nodeIndex;
let elDef = this.nodeDef;
let elView = view;
while (elIndex != null && view.def.nodes[elIndex].type !== NodeType.Element) {
elIndex = view.def.nodes[elIndex].parent;
while (elDef && elDef.type !== NodeType.Element) {
elDef = elDef.parent;
}
if (elIndex == null) {
while (elIndex == null && elView) {
elIndex = viewParentElIndex(elView);
if (!elDef) {
while (!elDef && elView) {
elDef = viewParentEl(elView);
elView = elView.parent;
}
}
this.elDef = elDef;
this.elView = elView;
if (elView) {
this.elDef = elView.def.nodes[elIndex];
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
const childDef = this.elView.def.nodes[i];
if (childDef.flags & NodeFlags.HasComponent) {
this.compProviderIndex = i;
break;
}
i += childDef.childCount;
}
} else {
this.elDef = null;
}
this.compProviderDef = elView ? this.elDef.element.component : null;
}
get injector(): Injector { return createInjector(this.elView, this.elDef.index); }
get injector(): Injector { return createInjector(this.elView, this.elDef); }
get component(): any {
if (this.compProviderIndex != null) {
return asProviderData(this.elView, this.compProviderIndex).instance;
if (this.compProviderDef) {
return asProviderData(this.elView, this.compProviderDef.index).instance;
}
return this.view.component;
}
get context(): any {
if (this.compProviderIndex != null) {
return asProviderData(this.elView, this.compProviderIndex).instance;
if (this.compProviderDef) {
return asProviderData(this.elView, this.compProviderDef.index).instance;
}
return this.view.context;
}
@@ -385,8 +373,8 @@ class DebugContext_ implements DebugContext {
}
}
get componentRenderElement() {
const view = this.compProviderIndex != null ?
asProviderData(this.elView, this.compProviderIndex).componentView :
const view = this.compProviderDef ?
asProviderData(this.elView, this.compProviderDef.index).componentView :
this.view;
const elData = findHostElement(view);
return elData ? elData.renderElement : undefined;
@@ -402,17 +390,14 @@ function findHostElement(view: ViewData): ElementData {
view = view.parent;
}
if (view.parent) {
const hostData = asElementData(view.parent, viewParentElIndex(view));
return hostData;
return asElementData(view.parent, viewParentEl(view).index);
}
return undefined;
}
function collectReferences(view: ViewData, nodeDef: NodeDef, references: {[key: string]: any}) {
for (let queryId in nodeDef.matchedQueries) {
if (queryIdIsReference(queryId)) {
references[queryId.slice(1)] = getQueryValue(view, nodeDef, queryId);
}
for (let refName in nodeDef.references) {
references[refName] = getQueryValue(view, nodeDef, nodeDef.references[refName]);
}
}
+10 -8
View File
@@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref';
import {looseIdentical} from '../facade/lang';
import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types';
import {checkAndUpdateBinding, sliceErrorStack} from './util';
import {checkAndUpdateBinding, getParentRenderElement, sliceErrorStack} from './util';
export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
// skip the call to sliceErrorStack itself + the call to this function.
@@ -31,13 +31,16 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
index: undefined,
reverseChildIndex: undefined,
parent: undefined,
childFlags: undefined,
childMatchedQueries: undefined,
renderParent: undefined,
bindingIndex: undefined,
disposableIndex: undefined,
// regular values
flags: 0,
matchedQueries: {}, ngContentIndex,
childFlags: 0,
childMatchedQueries: 0,
matchedQueries: {},
matchedQueryIds: 0,
references: {}, ngContentIndex,
childCount: 0, bindings,
disposableCount: 0,
element: undefined,
@@ -50,13 +53,12 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
}
export function createText(view: ViewData, renderHost: any, def: NodeDef): TextData {
const parentNode =
def.parent != null ? asElementData(view, def.parent).renderElement : renderHost;
let renderNode: any;
const renderer = view.root.renderer;
renderNode = renderer.createText(def.text.prefix);
if (parentNode) {
renderer.appendChild(parentNode, renderNode);
const parentEl = getParentRenderElement(view, renderHost, def);
if (parentEl) {
renderer.appendChild(parentEl, renderNode);
}
return {renderText: renderNode};
}
+36 -16
View File
@@ -43,10 +43,11 @@ export interface ViewDefinition {
bindingCount: number;
disposableCount: number;
/**
* ids of all queries that are matched by one of the nodes.
* Binary or of all query ids that are matched by one of the nodes.
* This includes query ids from templates as well.
* Used as a bloom filter.
*/
nodeMatchedQueries: {[queryId: string]: boolean};
nodeMatchedQueries: number;
}
export type ViewDefinitionFactory = () => ViewDefinition;
@@ -94,27 +95,35 @@ export interface NodeDef {
index: number;
reverseChildIndex: number;
flags: NodeFlags;
parent: number;
parent: NodeDef;
renderParent: NodeDef;
/** this is checked against NgContentDef.index to find matched nodes */
ngContentIndex: number;
/** number of transitive children */
childCount: number;
/** aggregated NodeFlags for all children **/
/** aggregated NodeFlags for all children (does not include self) **/
childFlags: NodeFlags;
bindingIndex: number;
bindings: BindingDef[];
disposableIndex: number;
disposableCount: number;
/**
* references that the user placed on the element
*/
references: {[refId: string]: QueryValueType};
/**
* ids and value types of all queries that are matched by this node.
*/
matchedQueries: {[queryId: string]: QueryValueType};
matchedQueries: {[queryId: number]: QueryValueType};
/** Binary or of all matched query ids of this node. */
matchedQueryIds: number;
/**
* ids of all queries that are matched by one of the child nodes.
* Binary or of all query ids that are matched by one of the children.
* This includes query ids from templates as well.
* Used as a bloom filter.
*/
childMatchedQueries: {[queryId: string]: boolean};
childMatchedQueries: number;
element: ElementDef;
provider: ProviderDef;
text: TextDef;
@@ -150,8 +159,11 @@ export enum NodeFlags {
HasEmbeddedViews = 1 << 8,
HasComponent = 1 << 9,
HasContentQuery = 1 << 10,
HasViewQuery = 1 << 11,
LazyProvider = 1 << 12
HasStaticQuery = 1 << 11,
HasDynamicQuery = 1 << 12,
HasViewQuery = 1 << 13,
LazyProvider = 1 << 14,
PrivateProvider = 1 << 15,
}
export interface BindingDef {
@@ -185,11 +197,17 @@ export interface ElementDef {
attrs: {[name: string]: string};
outputs: ElementOutputDef[];
template: ViewDefinition;
component: NodeDef;
/**
* visible providers for DI in the view,
* as see from this element.
* visible public providers for DI in the view,
* as see from this element. This does not include private providers.
*/
providerIndices: {[tokenKey: string]: number};
publicProviders: {[tokenKey: string]: NodeDef};
/**
* same as visiblePublicProviders, but also includes private providers
* that are located on this element.
*/
allProviders: {[tokenKey: string]: NodeDef};
source: string;
}
@@ -229,7 +247,7 @@ export enum DepFlags {
None = 0,
SkipSelf = 1 << 0,
Optional = 1 << 1,
Value = 2 << 2
Value = 2 << 2,
}
export interface DirectiveOutputDef {
@@ -251,7 +269,9 @@ export enum PureExpressionType {
}
export interface QueryDef {
id: string;
id: number;
// variant of the id that can be used to check against NodeDef.matchedQueryIds, ...
filterId: number;
bindings: QueryBindingDef[];
}
@@ -287,7 +307,7 @@ export interface ViewData {
def: ViewDefinition;
root: RootData;
// index of component provider / anchor.
parentIndex: number;
parentNodeDef: NodeDef;
parent: ViewData;
component: any;
context: any;
@@ -431,7 +451,7 @@ export interface Services {
moveEmbeddedView(elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData;
destroyView(view: ViewData): void;
resolveDep(
view: ViewData, requestNodeIndex: number, elIndex: number, depDef: DepDef,
view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef,
notFoundValue?: any): any;
createDebugContext(view: ViewData, nodeIndex: number): DebugContext;
handleEvent: ViewHandleEventFn;
+56 -11
View File
@@ -17,7 +17,7 @@ import {ViewRef} from '../linker/view_ref';
import {Renderer} from '../render/api';
import {expressionChangedAfterItHasBeenCheckedError, isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, NodeType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types';
import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types';
const _tokenKeyCache = new Map<any, string>();
@@ -85,7 +85,7 @@ export function dispatchEvent(
export function declaredViewContainer(view: ViewData): ElementData {
if (view.parent) {
const parentView = view.parent;
return asElementData(parentView, view.parentIndex);
return asElementData(parentView, view.parentNodeDef.index);
}
return undefined;
}
@@ -95,10 +95,10 @@ export function declaredViewContainer(view: ViewData): ElementData {
* for embedded views, this is the index of the parent node
* that contains the view container.
*/
export function viewParentElIndex(view: ViewData): number {
export function viewParentEl(view: ViewData): NodeDef {
const parentView = view.parent;
if (parentView) {
return parentView.def.nodes[view.parentIndex].parent;
return view.parentNodeDef.parent;
} else {
return null;
}
@@ -128,10 +128,6 @@ export function nodeValue(view: ViewData, index: number): any {
return undefined;
}
export function queryIdIsReference(queryId: string): boolean {
return queryId.startsWith('#');
}
export function elementEventFullName(target: string, name: string): string {
return target ? `${target}:${name}` : name;
}
@@ -140,6 +136,45 @@ export function isComponentView(view: ViewData): boolean {
return view.component === view.context && !!view.parent;
}
export function isEmbeddedView(view: ViewData): boolean {
return view.component !== view.context && !!view.parent;
}
export function filterQueryId(queryId: number): number {
return 1 << (queryId % 32);
}
export function splitMatchedQueriesDsl(matchedQueriesDsl: [string | number, QueryValueType][]): {
matchedQueries: {[queryId: string]: QueryValueType},
references: {[refId: string]: QueryValueType},
matchedQueryIds: number
} {
const matchedQueries: {[queryId: string]: QueryValueType} = {};
let matchedQueryIds = 0;
const references: {[refId: string]: QueryValueType} = {};
if (matchedQueriesDsl) {
matchedQueriesDsl.forEach(([queryId, valueType]) => {
if (typeof queryId === 'number') {
matchedQueries[queryId] = valueType;
matchedQueryIds |= filterQueryId(queryId);
} else {
references[queryId] = valueType;
}
});
}
return {matchedQueries, references, matchedQueryIds};
}
export function getParentRenderElement(view: ViewData, renderHost: any, def: NodeDef): any {
let parentEl: any;
if (!def.parent) {
parentEl = renderHost;
} else if (def.renderParent) {
parentEl = asElementData(view, def.renderParent.index).renderElement;
}
return parentEl;
}
const VIEW_DEFINITION_CACHE = new WeakMap<any, ViewDefinition>();
export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition {
@@ -187,9 +222,14 @@ export function visitRootRenderNodes(
if (action === RenderNodeAction.RemoveChild) {
parentNode = view.root.renderer.parentNode(renderNode(view, view.def.lastRootNode));
}
visitSiblingRenderNodes(
view, action, 0, view.def.nodes.length - 1, parentNode, nextSibling, target);
}
const len = view.def.nodes.length;
for (let i = 0; i < len; i++) {
export function visitSiblingRenderNodes(
view: ViewData, action: RenderNodeAction, startIndex: number, endIndex: number, parentNode: any,
nextSibling: any, target: any[]) {
for (let i = startIndex; i <= endIndex; i++) {
const nodeDef = view.def.nodes[i];
if (nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text ||
nodeDef.type === NodeType.NgContent) {
@@ -208,7 +248,7 @@ export function visitProjectedRenderNodes(
compView = compView.parent;
}
const hostView = compView.parent;
const hostElDef = hostView.def.nodes[viewParentElIndex(compView)];
const hostElDef = viewParentEl(compView);
const startIndex = hostElDef.index + 1;
const endIndex = hostElDef.index + hostElDef.childCount;
for (let i = startIndex; i <= endIndex; i++) {
@@ -247,6 +287,11 @@ function visitRenderNode(
}
}
}
if (nodeDef.type === NodeType.Element && !nodeDef.element.name) {
visitSiblingRenderNodes(
view, action, nodeDef.index + 1, nodeDef.index + nodeDef.childCount, parentNode,
nextSibling, target);
}
}
}
File diff suppressed because it is too large Load Diff
+4 -10
View File
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {dirtyParentQuery} from './query';
import {dirtyParentQueries} from './query';
import {ElementData, NodeData, NodeDef, NodeFlags, NodeType, ViewData, asElementData, asProviderData, asTextData} from './types';
import {RenderNodeAction, declaredViewContainer, isComponentView, renderNode, rootRenderNodes, visitProjectedRenderNodes, visitRootRenderNodes} from './util';
@@ -25,9 +25,7 @@ export function attachEmbeddedView(elementData: ElementData, viewIndex: number,
projectedViews.push(view);
}
for (let queryId in view.def.nodeMatchedQueries) {
dirtyParentQuery(queryId, view);
}
dirtyParentQueries(view);
const prevView = viewIndex > 0 ? embeddedViews[viewIndex - 1] : null;
renderAttachEmbeddedView(elementData, prevView, view);
@@ -47,9 +45,7 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex: number):
removeFromArray(projectedViews, projectedViews.indexOf(view));
}
for (let queryId in view.def.nodeMatchedQueries) {
dirtyParentQuery(queryId, view);
}
dirtyParentQueries(view);
renderDetachEmbeddedView(elementData, view);
@@ -69,9 +65,7 @@ export function moveEmbeddedView(
// Note: Don't need to change projectedViews as the order in there
// as always invalid...
for (let queryId in view.def.nodeMatchedQueries) {
dirtyParentQuery(queryId, view);
}
dirtyParentQueries(view);
renderDetachEmbeddedView(elementData, view);
const prevView = newViewIndex > 0 ? embeddedViews[newViewIndex - 1] : null;
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver} from '@angular/core';
import {noComponentFactoryError} from '@angular/core/src/linker/component_factory_resolver';
import {TestBed} from '@angular/core/testing';
@@ -14,6 +15,19 @@ import {Console} from '../../src/console';
export function main() {
describe('Current compiler', () => { createTests({viewEngine: false}); });
describe('View Engine compiler', () => {
beforeEach(() => {
TestBed.configureCompiler(
{useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]});
});
createTests({viewEngine: true});
});
}
function createTests({viewEngine}: {viewEngine: boolean}) {
describe('jit', () => { declareTests({useJit: true}); });
describe('no jit', () => { declareTests({useJit: false}); });
}
@@ -1250,7 +1250,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
.toThrowError(`Directive ${stringify(SomeDirective)} has no selector, please add it!`);
});
viewEngine || it('should use a default element name for components without selectors', () => {
it('should use a default element name for components without selectors', () => {
let noSelectorComponentFactory: ComponentFactory<SomeComponent>;
@Component({template: '----'})
@@ -6,14 +6,27 @@
* found in the LICENSE file at https://angular.io/license
*/
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
import {Component, Directive, ElementRef, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {beforeEach, ddescribe, describe, iit, it} from '@angular/core/testing/testing_internal';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
describe('Current compiler', () => { createTests({viewEngine: false}); });
describe('View Engine compiler', () => {
beforeEach(() => {
TestBed.configureCompiler(
{useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]});
});
createTests({viewEngine: true});
});
}
function createTests({viewEngine}: {viewEngine: boolean}) {
describe('projection', () => {
beforeEach(() => TestBed.configureTestingModule({declarations: [MainComp, OtherComp, Simple]}));
@@ -365,7 +378,7 @@ export function main() {
expect(main.nativeElement).toHaveText('TREE(0:TREE2(1:TREE(2:)))');
});
if (getDOM().supportsNativeShadowDOM()) {
if (!viewEngine && getDOM().supportsNativeShadowDOM()) {
it('should support native content projection and isolate styles per component', () => {
TestBed.configureTestingModule({declarations: [SimpleNative1, SimpleNative2]});
TestBed.overrideComponent(MainComp, {
@@ -383,7 +396,7 @@ export function main() {
});
}
if (getDOM().supportsDOMEvents()) {
if (!viewEngine && getDOM().supportsDOMEvents()) {
it('should support non emulated styles', () => {
TestBed.configureTestingModule({declarations: [OtherComp]});
TestBed.overrideComponent(MainComp, {
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, asNativeElements} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';
@@ -13,6 +14,19 @@ import {expect} from '@angular/platform-browser/testing/matchers';
import {stringify} from '../../src/facade/lang';
export function main() {
describe('Current compiler', () => { createTests({viewEngine: false}); });
describe('View Engine compiler', () => {
beforeEach(() => {
TestBed.configureCompiler(
{useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]});
});
createTests({viewEngine: true});
});
}
function createTests({viewEngine}: {viewEngine: boolean}) {
describe('Query API', () => {
beforeEach(() => TestBed.configureTestingModule({
@@ -267,9 +281,10 @@ export function main() {
it('should contain the first descendant content child templateRef', () => {
const template = '<needs-content-child-template-ref-app>' +
'</needs-content-child-template-ref-app>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmp(MyComp0, template);
view.detectChanges();
// can't execute checkNoChanges as our view modifies our content children (via a query).
view.detectChanges(false);
expect(view.nativeElement).toHaveText('OUTER');
});
@@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, ElementRef, Host, Inject, Input, Optional, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewContainerRef} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
import {beforeEach, beforeEachProviders, describe, it} from '@angular/core/testing/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers';
@@ -190,6 +190,19 @@ class TestComp {
}
export function main() {
describe('Current compiler', () => { createTests({viewEngine: false}); });
describe('View Engine compiler', () => {
beforeEach(() => {
TestBed.configureCompiler(
{useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]});
});
createTests({viewEngine: true});
});
}
function createTests({viewEngine}: {viewEngine: boolean}) {
function createComponentFixture<T>(
template: string, providers: Provider[] = null, comp: Type<T> = null): ComponentFixture<T> {
if (!comp) {
@@ -213,9 +226,10 @@ export function main() {
// On CJS fakeAsync is not supported...
if (!getDOM().supportsDOMEvents()) return;
beforeEach(() => TestBed.configureTestingModule({declarations: [TestComp]}));
beforeEachProviders(() => [{provide: 'appService', useValue: 'appService'}]);
beforeEach(() => TestBed.configureTestingModule({
declarations: [TestComp],
providers: [{provide: 'appService', useValue: 'appService'}]
}));
describe('injection', () => {
it('should instantiate directives that have no dependencies', () => {
@@ -591,20 +605,19 @@ export function main() {
.toBe(el.children[0].nativeElement);
});
it('should inject ChangeDetectorRef of the component\'s view into the component via a proxy',
() => {
TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]});
const cf = createComponentFixture('<div componentNeedsChangeDetectorRef></div>');
cf.detectChanges();
const compEl = cf.debugElement.children[0];
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
comp.counter = 1;
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0');
comp.changeDetectorRef.markForCheck();
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('1');
});
it('should inject ChangeDetectorRef of the component\'s view into the component', () => {
TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]});
const cf = createComponentFixture('<div componentNeedsChangeDetectorRef></div>');
cf.detectChanges();
const compEl = cf.debugElement.children[0];
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
comp.counter = 1;
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0');
comp.changeDetectorRef.markForCheck();
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('1');
});
it('should inject ChangeDetectorRef of the containing component into directives', () => {
TestBed.configureTestingModule(
@@ -624,9 +637,9 @@ export function main() {
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0');
expect(compEl.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef)
.toBe(comp.changeDetectorRef);
.toEqual(comp.changeDetectorRef);
expect(compEl.children[1].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef)
.toBe(comp.changeDetectorRef);
.toEqual(comp.changeDetectorRef);
comp.changeDetectorRef.markForCheck();
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('1');
@@ -687,7 +700,7 @@ export function main() {
'<div [simpleDirective]="true | pipeNeedsChangeDetectorRef" directiveNeedsChangeDetectorRef></div>');
const cdRef =
el.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef;
expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toBe(cdRef);
expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toEqual(cdRef);
});
it('should cache pure pipes', () => {
+32 -13
View File
@@ -33,6 +33,8 @@ export function main() {
return {rootNodes, view};
}
const someQueryId = 1;
class AService {}
class QueryService {
@@ -42,19 +44,24 @@ export function main() {
function contentQueryProviders() {
return [
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All})
queryDef(
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.All})
];
}
function viewQueryProviders(compView: ViewDefinition) {
return [
directiveDef(NodeFlags.None, null, 1, QueryService, [], null, null, () => compView),
queryDef(NodeFlags.HasViewQuery, 'query1', {'a': QueryBindingType.All})
queryDef(
NodeFlags.HasViewQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.All})
];
}
function aServiceProvider() {
return directiveDef(NodeFlags.None, [['query1', QueryValueType.Provider]], 0, AService, []);
return directiveDef(
NodeFlags.None, [[someQueryId, QueryValueType.Provider]], 0, AService, []);
}
describe('content queries', () => {
@@ -251,7 +258,9 @@ export function main() {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 4, 'div'),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All}),
queryDef(
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.All}),
aServiceProvider(),
aServiceProvider(),
]));
@@ -274,7 +283,9 @@ export function main() {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 4, 'div'),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}),
queryDef(
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.First}),
aServiceProvider(),
aServiceProvider(),
]));
@@ -293,9 +304,11 @@ export function main() {
}
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, [['query1', QueryValueType.ElementRef]], null, 2, 'div'),
elementDef(NodeFlags.None, [[someQueryId, QueryValueType.ElementRef]], null, 2, 'div'),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}),
queryDef(
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.First}),
]));
Services.checkAndUpdateView(view);
@@ -311,10 +324,12 @@ export function main() {
const {view} = createAndGetRootNodes(compViewDef([
anchorDef(
NodeFlags.None, [['query1', QueryValueType.TemplateRef]], null, 2,
NodeFlags.None, [[someQueryId, QueryValueType.TemplateRef]], null, 2,
embeddedViewDef([anchorDef(NodeFlags.None, null, null, 0)])),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}),
queryDef(
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.First}),
]));
Services.checkAndUpdateView(view);
@@ -329,9 +344,11 @@ export function main() {
}
const {view} = createAndGetRootNodes(compViewDef([
anchorDef(NodeFlags.None, [['query1', QueryValueType.ViewContainerRef]], null, 2),
anchorDef(NodeFlags.None, [[someQueryId, QueryValueType.ViewContainerRef]], null, 2),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}),
queryDef(
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.First}),
]));
Services.checkAndUpdateView(view);
@@ -367,7 +384,7 @@ export function main() {
expect(err).toBeTruthy();
expect(err.message)
.toBe(
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'Query query1 not dirty'. Current value: 'Query query1 dirty'.`);
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'Query 1 not dirty'. Current value: 'Query 1 dirty'.`);
const debugCtx = getDebugContext(err);
expect(debugCtx.view).toBe(view);
expect(debugCtx.nodeIndex).toBe(2);
@@ -381,7 +398,9 @@ export function main() {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 3, 'div'),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All}),
queryDef(
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.All}),
aServiceProvider(),
]));
@@ -39,7 +39,7 @@ export function main() {
directiveDef(
NodeFlags.None, null, 0, AComp, [], null, null,
() => compViewDef([
elementDef(NodeFlags.None, [['#ref', QueryValueType.ElementRef]], null, 2, 'span'),
elementDef(NodeFlags.None, [['ref', QueryValueType.ElementRef]], null, 2, 'span'),
directiveDef(NodeFlags.None, null, 0, AService, []), textDef(null, ['a'])
])),
]));
@@ -7,6 +7,7 @@
*/
import {NodeFlags, QueryValueType, ViewData, ViewDefinition, ViewFlags, anchorDef, directiveDef, elementDef, textDef, viewDef} from '@angular/core/src/view/index';
import {filterQueryId} from '@angular/core/src/view/util';
export function main() {
describe('viewDef', () => {
@@ -76,7 +77,7 @@ export function main() {
describe('parent', () => {
function parents(viewDef: ViewDefinition): number[] {
return viewDef.nodes.map(node => node.parent);
return viewDef.nodes.map(node => node.parent ? node.parent.index : null);
}
it('should calculate parents for one level', () => {
@@ -86,7 +87,7 @@ export function main() {
textDef(null, ['a']),
]);
expect(parents(vd)).toEqual([undefined, 0, 0]);
expect(parents(vd)).toEqual([null, 0, 0]);
});
it('should calculate parents for one level, multiple roots', () => {
@@ -98,7 +99,7 @@ export function main() {
textDef(null, ['a']),
]);
expect(parents(vd)).toEqual([undefined, 0, undefined, 2, undefined]);
expect(parents(vd)).toEqual([null, 0, null, 2, null]);
});
it('should calculate parents for multiple levels', () => {
@@ -111,7 +112,7 @@ export function main() {
textDef(null, ['a']),
]);
expect(parents(vd)).toEqual([undefined, 0, 1, undefined, 3, undefined]);
expect(parents(vd)).toEqual([null, 0, 1, null, 3, null]);
});
});
@@ -175,52 +176,56 @@ export function main() {
});
describe('childMatchedQueries', () => {
function childMatchedQueries(viewDef: ViewDefinition): string[][] {
return viewDef.nodes.map(node => Object.keys(node.childMatchedQueries).sort());
function childMatchedQueries(viewDef: ViewDefinition): number[] {
return viewDef.nodes.map(node => node.childMatchedQueries);
}
it('should calculate childMatchedQueries for one level', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, [])
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, [])
]);
expect(childMatchedQueries(vd)).toEqual([['q1'], []]);
expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), 0]);
});
it('should calculate childMatchedQueries for two levels', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 2, 'span'),
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, [])
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, [])
]);
expect(childMatchedQueries(vd)).toEqual([['q1'], ['q1'], []]);
expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), filterQueryId(1), 0]);
});
it('should calculate childMatchedQueries for one level, multiple roots', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, []),
elementDef(NodeFlags.None, null, null, 2, 'span'),
directiveDef(NodeFlags.None, [['q2', QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [['q3', QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [[2, QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [[3, QueryValueType.Provider]], 0, AService, []),
]);
expect(childMatchedQueries(vd)).toEqual([['q1'], [], ['q2', 'q3'], [], []]);
expect(childMatchedQueries(vd)).toEqual([
filterQueryId(1), 0, filterQueryId(2) | filterQueryId(3), 0, 0
]);
});
it('should calculate childMatchedQueries for multiple levels', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 2, 'span'),
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, []),
elementDef(NodeFlags.None, null, null, 2, 'span'),
directiveDef(NodeFlags.None, [['q2', QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [['q3', QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [[2, QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [[3, QueryValueType.Provider]], 0, AService, []),
]);
expect(childMatchedQueries(vd)).toEqual([['q1'], ['q1'], [], ['q2', 'q3'], [], []]);
expect(childMatchedQueries(vd)).toEqual([
filterQueryId(1), filterQueryId(1), 0, filterQueryId(2) | filterQueryId(3), 0, 0
]);
});
it('should included embedded views into childMatchedQueries', () => {
@@ -231,12 +236,12 @@ export function main() {
() => viewDef(
ViewFlags.None,
[
elementDef(NodeFlags.None, [['q1', QueryValueType.Provider]], null, 0, 'span'),
elementDef(NodeFlags.None, [[1, QueryValueType.Provider]], null, 0, 'span'),
]))
]);
// Note: the template will become a sibling to the anchor once stamped out,
expect(childMatchedQueries(vd)).toEqual([['q1'], []]);
expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), 0]);
});
});
});
@@ -389,7 +389,11 @@ export class DomRendererV2 implements RendererV2 {
}
}
removeChild(parent: any, oldChild: any): void { parent.removeChild(oldChild); }
removeChild(parent: any, oldChild: any): void {
if (parent) {
parent.removeChild(oldChild);
}
}
selectRootElement(selectorOrNode: string|any, debugInfo?: any): any {
let el: any = typeof selectorOrNode === 'string' ? document.querySelector(selectorOrNode) :
@@ -259,7 +259,11 @@ export class ServerRendererV2 implements RendererV2 {
}
}
removeChild(parent: any, oldChild: any): void { getDOM().removeChild(parent, oldChild); }
removeChild(parent: any, oldChild: any): void {
if (parent) {
getDOM().removeChild(parent, oldChild);
}
}
selectRootElement(selectorOrNode: string|any, debugInfo?: any): any {
let el: any;