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

View File

@ -304,6 +304,7 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
providers: CompileProviderMetadata[]; providers: CompileProviderMetadata[];
viewProviders: CompileProviderMetadata[]; viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[]; queries: CompileQueryMetadata[];
viewQueries: CompileQueryMetadata[];
entryComponents: CompileEntryComponentMetadata[]; entryComponents: CompileEntryComponentMetadata[];
changeDetection: ChangeDetectionStrategy; changeDetection: ChangeDetectionStrategy;
template: CompileTemplateSummary; template: CompileTemplateSummary;
@ -480,6 +481,7 @@ export class CompileDirectiveMetadata {
providers: this.providers, providers: this.providers,
viewProviders: this.viewProviders, viewProviders: this.viewProviders,
queries: this.queries, queries: this.queries,
viewQueries: this.viewQueries,
entryComponents: this.entryComponents, entryComponents: this.entryComponents,
changeDetection: this.changeDetection, changeDetection: this.changeDetection,
template: this.template && this.template.toSummary(), template: this.template && this.template.toSummary(),

View File

@ -368,6 +368,12 @@ export class Identifiers {
{name: 'ɵviewEngine', moduleUrl: CORE, member: 'pipeDef', runtime: ɵviewEngine.pipeDef}; {name: 'ɵviewEngine', moduleUrl: CORE, member: 'pipeDef', runtime: ɵviewEngine.pipeDef};
static nodeValue: IdentifierSpec = static nodeValue: IdentifierSpec =
{name: 'ɵviewEngine', moduleUrl: CORE, member: 'nodeValue', runtime: ɵviewEngine.nodeValue}; {name: 'ɵviewEngine', moduleUrl: CORE, member: 'nodeValue', runtime: ɵviewEngine.nodeValue};
static ngContentDef: IdentifierSpec = {
name: 'ɵviewEngine',
moduleUrl: CORE,
member: 'ngContentDef',
runtime: ɵviewEngine.ngContentDef
};
static unwrapValue: IdentifierSpec = { static unwrapValue: IdentifierSpec = {
name: 'ɵviewEngine', name: 'ɵviewEngine',
moduleUrl: CORE, moduleUrl: CORE,

View File

@ -11,7 +11,7 @@ import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveS
import {isBlank, isPresent} from './facade/lang'; import {isBlank, isPresent} from './facade/lang';
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers'; import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
import {ParseError, ParseSourceSpan} from './parse_util'; 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 { export class ProviderError extends ParseError {
constructor(message: string, span: ParseSourceSpan) { super(span, message); } constructor(message: string, span: ParseSourceSpan) { super(span, message); }
@ -19,7 +19,7 @@ export class ProviderError extends ParseError {
export interface QueryWithId { export interface QueryWithId {
meta: CompileQueryMetadata; meta: CompileQueryMetadata;
id: QueryId; queryId: number;
} }
export class ProviderViewContext { export class ProviderViewContext {
@ -57,16 +57,21 @@ export class ProviderElementContext {
constructor( constructor(
public viewContext: ProviderViewContext, private _parent: ProviderElementContext, public viewContext: ProviderViewContext, private _parent: ProviderElementContext,
private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[], private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[],
refs: ReferenceAst[], private _sourceSpan: ParseSourceSpan) { refs: ReferenceAst[], isTemplate: boolean, contentQueryStartId: number,
private _sourceSpan: ParseSourceSpan) {
this._attrs = {}; this._attrs = {};
attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value); attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value);
const directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive); const directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive);
this._allProviders = this._allProviders =
_resolveProvidersFromDirectives(directivesMeta, _sourceSpan, viewContext.errors); _resolveProvidersFromDirectives(directivesMeta, _sourceSpan, viewContext.errors);
this._contentQueries = _getContentQueries(this.depth, directivesMeta); this._contentQueries = _getContentQueries(contentQueryStartId, directivesMeta);
Array.from(this._allProviders.values()).forEach((provider) => { Array.from(this._allProviders.values()).forEach((provider) => {
this._addQueryReadsTo(provider.token, provider.token, this._queriedTokens); this._addQueryReadsTo(provider.token, provider.token, this._queriedTokens);
}); });
if (isTemplate) {
const templateRefId = createIdentifierToken(Identifiers.TemplateRef);
this._addQueryReadsTo(templateRefId, templateRefId, this._queriedTokens);
}
refs.forEach((refAst) => { refs.forEach((refAst) => {
let defaultQueryValue = refAst.value || createIdentifierToken(Identifiers.ElementRef); let defaultQueryValue = refAst.value || createIdentifierToken(Identifiers.ElementRef);
this._addQueryReadsTo({value: refAst.name}, defaultQueryValue, this._queriedTokens); 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[] { get transformProviders(): ProviderAst[] {
return Array.from(this._transformedProviders.values()); return Array.from(this._transformedProviders.values());
} }
@ -133,7 +128,7 @@ export class ProviderElementContext {
queryMatches = []; queryMatches = [];
queryReadTokens.set(tokenRef, 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[]> { 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[]>(); const viewQueries = new Map<any, QueryWithId[]>();
if (component.viewQueries) { if (component.viewQueries) {
component.viewQueries.forEach( component.viewQueries.forEach(
(query, queryIndex) => _addQueryToTokenMap( (query) => _addQueryToTokenMap(viewQueries, {meta: query, queryId: viewQueryId++}));
viewQueries,
{meta: query, id: {elementDepth: null, directiveIndex: null, queryIndex: queryIndex}}));
} }
return viewQueries; return viewQueries;
} }
function _getContentQueries( 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[]>(); const contentQueries = new Map<any, QueryWithId[]>();
directives.forEach((directive, directiveIndex) => { directives.forEach((directive, directiveIndex) => {
if (directive.queries) { if (directive.queries) {
directive.queries.forEach( directive.queries.forEach(
(query, queryIndex) => _addQueryToTokenMap( (query) => _addQueryToTokenMap(contentQueries, {meta: query, queryId: contentQueryId++}));
contentQueries, {meta: query, id: {elementDepth, directiveIndex, queryIndex}}));
} }
}); });
return contentQueries; return contentQueries;

View File

@ -170,7 +170,7 @@ export class DirectiveAst implements TemplateAst {
constructor( constructor(
public directive: CompileDirectiveSummary, public inputs: BoundDirectivePropertyAst[], public directive: CompileDirectiveSummary, public inputs: BoundDirectivePropertyAst[],
public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[], public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[],
public sourceSpan: ParseSourceSpan) {} public contentQueryStartId: number, public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitDirective(this, context); return visitor.visitDirective(this, context);
} }
@ -241,17 +241,8 @@ export enum PropertyBindingType {
Animation 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 { export interface QueryMatch {
query: QueryId; queryId: number;
value: CompileTokenMetadata; value: CompileTokenMetadata;
} }

View File

@ -207,12 +207,15 @@ export class TemplateParser {
class TemplateParseVisitor implements html.Visitor { class TemplateParseVisitor implements html.Visitor {
selectorMatcher = new SelectorMatcher(); selectorMatcher = new SelectorMatcher();
directivesIndex = new Map<CompileDirectiveSummary, number>(); directivesIndex = new Map<CompileDirectiveSummary, number>();
ngContentCount: number = 0; ngContentCount = 0;
contentQueryStartId: number;
constructor( constructor(
public providerViewContext: ProviderViewContext, directives: CompileDirectiveSummary[], public providerViewContext: ProviderViewContext, directives: CompileDirectiveSummary[],
private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry, private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry,
private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) { 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) => { directives.forEach((directive, index) => {
const selector = CssSelector.parse(directive.selector); const selector = CssSelector.parse(directive.selector);
this.selectorMatcher.addSelectables(selector, directive); this.selectorMatcher.addSelectables(selector, directive);
@ -241,6 +244,7 @@ class TemplateParseVisitor implements html.Visitor {
visitComment(comment: html.Comment, context: any): any { return null; } visitComment(comment: html.Comment, context: any): any { return null; }
visitElement(element: html.Element, parent: ElementContext): any { visitElement(element: html.Element, parent: ElementContext): any {
const queryStartIndex = this.contentQueryStartId;
const nodeName = element.name; const nodeName = element.name;
const preparsedElement = preparseElement(element); const preparsedElement = preparseElement(element);
if (preparsedElement.type === PreparsedElementType.SCRIPT || if (preparsedElement.type === PreparsedElementType.SCRIPT ||
@ -322,9 +326,9 @@ class TemplateParseVisitor implements html.Visitor {
const providerContext = new ProviderElementContext( const providerContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs, 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, preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
ElementContext.create( ElementContext.create(
isTemplateElement, directiveAsts, isTemplateElement, directiveAsts,
@ -378,6 +382,7 @@ class TemplateParseVisitor implements html.Visitor {
} }
if (hasInlineTemplates) { if (hasInlineTemplates) {
const templateQueryStartIndex = this.contentQueryStartId;
const templateCssSelector = const templateCssSelector =
createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs); createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
const {directives: templateDirectiveMetas} = const {directives: templateDirectiveMetas} =
@ -392,7 +397,7 @@ class TemplateParseVisitor implements html.Visitor {
templateDirectiveAsts, templateElementProps, element.sourceSpan); templateDirectiveAsts, templateElementProps, element.sourceSpan);
const templateProviderContext = new ProviderElementContext( const templateProviderContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, parent.isTemplateElement, this.providerViewContext, parent.providerContext, parent.isTemplateElement,
templateDirectiveAsts, [], [], element.sourceSpan); templateDirectiveAsts, [], [], true, templateQueryStartIndex, element.sourceSpan);
templateProviderContext.afterElement(); templateProviderContext.afterElement();
parsedElement = new EmbeddedTemplateAst( parsedElement = new EmbeddedTemplateAst(
@ -585,8 +590,11 @@ class TemplateParseVisitor implements html.Visitor {
matchedReferences.add(elOrDirRef.name); matchedReferences.add(elOrDirRef.name);
} }
}); });
const contentQueryStartId = this.contentQueryStartId;
this.contentQueryStartId += directive.queries.length;
return new DirectiveAst( return new DirectiveAst(
directive, directiveProperties, hostProperties, hostEvents, sourceSpan); directive, directiveProperties, hostProperties, hostEvents, contentQueryStartId,
sourceSpan);
}); });
elementOrDirectiveRefs.forEach((elOrDirRef) => { 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 attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]);
const selector = createElementCssSelector(ast.name, attrNameAndValues); const selector = createElementCssSelector(ast.name, attrNameAndValues);
const ngContentIndex = parent.findNgContentIndex(selector); 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( return new ElementAst(
ast.name, html.visitAll(this, ast.attrs), [], [], [], [], [], false, [], children, ast.name, html.visitAll(this, ast.attrs), [], [], [], [], [], false, [], children,
ngContentIndex, ast.sourceSpan, ast.endSourceSpan); ngContentIndex, ast.sourceSpan, ast.endSourceSpan);

View File

@ -9,23 +9,24 @@
import {ChangeDetectionStrategy} from '@angular/core'; import {ChangeDetectionStrategy} from '@angular/core';
import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenReference} from '../compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, CompileTypeMetadata, identifierModuleUrl, identifierName, tokenReference} from '../compile_metadata';
import {BuiltinConverter, BuiltinConverterFactory, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter'; import {BuiltinConverter, BuiltinConverterFactory, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast'; import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
import {Identifiers, createIdentifier, resolveIdentifier} from '../identifiers'; import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from '../identifiers';
import {CompilerInjectable} from '../injectable'; import {CompilerInjectable} from '../injectable';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {convertValueToOutputAst} from '../output/value_util'; import {convertValueToOutputAst} from '../output/value_util';
import {LifecycleHooks, viewEngine} from '../private_import_core'; import {LifecycleHooks, viewEngine} from '../private_import_core';
import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryId, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
import {ViewEncapsulationEnum} from '../view_compiler/constants'; import {ViewEncapsulationEnum} from '../view_compiler/constants';
import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler'; import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
const CLASS_ATTR = 'class'; const CLASS_ATTR = 'class';
const STYLE_ATTR = 'style'; const STYLE_ATTR = 'style';
const IMPLICIT_TEMPLATE_VAR = '\$implicit'; const IMPLICIT_TEMPLATE_VAR = '\$implicit';
const NG_CONTAINER_TAG = 'ng-container';
@CompilerInjectable() @CompilerInjectable()
export class ViewCompilerNext extends ViewCompiler { export class ViewCompilerNext extends ViewCompiler {
@ -41,15 +42,16 @@ export class ViewCompilerNext extends ViewCompiler {
const compName = identifierName(component.type) + (component.isHost ? `_Host` : ''); const compName = identifierName(component.type) + (component.isHost ? `_Host` : '');
let embeddedViewCount = 0; let embeddedViewCount = 0;
const staticQueryIds = findStaticQueryIds(template);
const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => { const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => {
const embeddedViewIndex = embeddedViewCount++; const embeddedViewIndex = embeddedViewCount++;
const viewName = `view_${compName}_${embeddedViewIndex}`; const viewName = `view_${compName}_${embeddedViewIndex}`;
return new ViewBuilder(parent, viewName, usedPipes, viewBuilderFactory); return new ViewBuilder(parent, viewName, usedPipes, staticQueryIds, viewBuilderFactory);
}; };
const visitor = viewBuilderFactory(null); const visitor = viewBuilderFactory(null);
visitor.visitAll([], template, 0); visitor.visitAll([], template);
const statements: o.Statement[] = []; const statements: o.Statement[] = [];
statements.push(...visitor.build(component)); statements.push(...visitor.build(component));
@ -93,9 +95,10 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
constructor( constructor(
private parent: ViewBuilder, public viewName: string, private usedPipes: CompilePipeSummary[], private parent: ViewBuilder, public viewName: string, private usedPipes: CompilePipeSummary[],
private staticQueryIds: Map<TemplateAst, StaticAndDynamicQueryIds>,
private viewBuilderFactory: ViewBuilderFactory) {} private viewBuilderFactory: ViewBuilderFactory) {}
visitAll(variables: VariableAst[], astNodes: TemplateAst[], elementDepth: number) { visitAll(variables: VariableAst[], astNodes: TemplateAst[]) {
this.variables = variables; this.variables = variables;
// create the pipes for the pure pipes immediately, so that we know their indices. // create the pipes for the pure pipes immediately, so that we know their indices.
if (!this.parent) { if (!this.parent) {
@ -106,7 +109,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
}); });
} }
templateVisitAll(this, astNodes, {elementDepth}); templateVisitAll(this, astNodes);
if (astNodes.length === 0 || if (astNodes.length === 0 ||
(this.parent && needsAdditionalRootNode(astNodes[astNodes.length - 1]))) { (this.parent && needsAdditionalRootNode(astNodes[astNodes.length - 1]))) {
// if the view is empty, or an embedded view has a view container as last root nde, // if the view is empty, or an embedded view has a view container as last root nde,
@ -197,12 +200,17 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
return updateFn; return updateFn;
} }
visitNgContent(ast: NgContentAst, context: any): any {} visitNgContent(ast: NgContentAst, context: any): any {
// ngContentDef(ngContentIndex: number, index: number): NodeDef;
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.ngContentDef)).callFn([
o.literal(ast.ngContentIndex), o.literal(ast.index)
]));
}
visitText(ast: TextAst, context: any): any { visitText(ast: TextAst, context: any): any {
// textDef(ngContentIndex: number, constants: string[]): NodeDef; // textDef(ngContentIndex: number, constants: string[]): NodeDef;
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.textDef)).callFn([ this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
o.NULL_EXPR, o.literalArr([o.literal(ast.value)]) o.literal(ast.ngContentIndex), o.literalArr([o.literal(ast.value)])
])); ]));
} }
@ -220,20 +228,20 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
// textDef(ngContentIndex: number, constants: string[]): NodeDef; // textDef(ngContentIndex: number, constants: string[]): NodeDef;
this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.textDef)).callFn([ this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
o.NULL_EXPR, o.literalArr(inter.strings.map(s => o.literal(s))) o.literal(ast.ngContentIndex), o.literalArr(inter.strings.map(s => o.literal(s)))
]); ]);
} }
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: {elementDepth: number}): any { visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
const nodeIndex = this.nodeDefs.length; const nodeIndex = this.nodeDefs.length;
// reserve the space in the nodeDefs array // reserve the space in the nodeDefs array
this.nodeDefs.push(null); this.nodeDefs.push(null);
const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast, context); const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast);
const childVisitor = this.viewBuilderFactory(this); const childVisitor = this.viewBuilderFactory(this);
this.children.push(childVisitor); this.children.push(childVisitor);
childVisitor.visitAll(ast.variables, ast.children, context.elementDepth + 1); childVisitor.visitAll(ast.variables, ast.children);
const childCount = this.nodeDefs.length - nodeIndex - 1; const childCount = this.nodeDefs.length - nodeIndex - 1;
@ -241,20 +249,20 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
// childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef; // childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef;
this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([ this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([
o.literal(flags), queryMatchesExpr, o.NULL_EXPR, o.literal(childCount), o.literal(flags), queryMatchesExpr, o.literal(ast.ngContentIndex), o.literal(childCount),
o.variable(childVisitor.viewName) o.variable(childVisitor.viewName)
]); ]);
} }
visitElement(ast: ElementAst, context: {elementDepth: number}): any { visitElement(ast: ElementAst, context: any): any {
const nodeIndex = this.nodeDefs.length; const nodeIndex = this.nodeDefs.length;
// reserve the space in the nodeDefs array so we can add children // reserve the space in the nodeDefs array so we can add children
this.nodeDefs.push(null); this.nodeDefs.push(null);
const {flags, usedEvents, queryMatchesExpr, hostBindings} = let {flags, usedEvents, queryMatchesExpr, hostBindings} =
this._visitElementOrTemplate(nodeIndex, ast, context); this._visitElementOrTemplate(nodeIndex, ast);
templateVisitAll(this, ast.children, {elementDepth: context.elementDepth + 1}); templateVisitAll(this, ast.children);
ast.inputs.forEach( ast.inputs.forEach(
(inputAst) => { hostBindings.push({context: COMP_VAR, value: inputAst.value}); }); (inputAst) => { hostBindings.push({context: COMP_VAR, value: inputAst.value}); });
@ -270,6 +278,12 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
const childCount = this.nodeDefs.length - nodeIndex - 1; const childCount = this.nodeDefs.length - nodeIndex - 1;
let elName = ast.name;
if (ast.name === NG_CONTAINER_TAG) {
// Using a null element name creates an anchor.
elName = null;
}
// elementDef( // elementDef(
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
// childCount: number, name: string, fixedAttrs: {[name: string]: string} = {}, // childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
@ -279,22 +293,21 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
// SecurityContext])[], // SecurityContext])[],
// outputs?: (string | [string, string])[]): NodeDef; // outputs?: (string | [string, string])[]): NodeDef;
this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([ this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([
o.literal(flags), queryMatchesExpr, o.NULL_EXPR, o.literal(childCount), o.literal(ast.name), o.literal(flags), queryMatchesExpr, o.literal(ast.ngContentIndex), o.literal(childCount),
fixedAttrsDef(ast), inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR, o.literal(elName), fixedAttrsDef(ast),
inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR,
outputDefs.length ? o.literalArr(outputDefs) : o.NULL_EXPR outputDefs.length ? o.literalArr(outputDefs) : o.NULL_EXPR
]); ]);
} }
private _visitElementOrTemplate( private _visitElementOrTemplate(nodeIndex: number, ast: {
nodeIndex: number, ast: { hasViewContainer: boolean,
hasViewContainer: boolean, outputs: BoundEventAst[],
outputs: BoundEventAst[], directives: DirectiveAst[],
directives: DirectiveAst[], providers: ProviderAst[],
providers: ProviderAst[], references: ReferenceAst[],
references: ReferenceAst[], queryMatches: QueryMatch[]
queryMatches: QueryMatch[] }): {
},
context: {elementDepth: number}): {
flags: number, flags: number,
usedEvents: [string, string][], usedEvents: [string, string][],
queryMatchesExpr: o.Expression, queryMatchesExpr: o.Expression,
@ -317,19 +330,24 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
}); });
const hostBindings: {value: AST, context: o.Expression}[] = []; const hostBindings: {value: AST, context: o.Expression}[] = [];
const hostEvents: {context: o.Expression, eventAst: BoundEventAst}[] = []; const hostEvents: {context: o.Expression, eventAst: BoundEventAst}[] = [];
const componentFactoryResolverProvider = createComponentFactoryResolver(ast.directives);
if (componentFactoryResolverProvider) {
this._visitProvider(componentFactoryResolverProvider, ast.queryMatches);
}
ast.providers.forEach((providerAst, providerIndex) => { ast.providers.forEach((providerAst, providerIndex) => {
let dirAst: DirectiveAst; let dirAst: DirectiveAst;
let dirIndex: number; let dirIndex: number;
ast.directives.forEach((localDirAst, i) => { ast.directives.forEach((localDirAst, i) => {
if (localDirAst.directive.type.reference === providerAst.token.identifier.reference) { if (localDirAst.directive.type.reference === tokenReference(providerAst.token)) {
dirAst = localDirAst; dirAst = localDirAst;
dirIndex = i; dirIndex = i;
} }
}); });
if (dirAst) { if (dirAst) {
const {hostBindings: dirHostBindings, hostEvents: dirHostEvents} = this._visitDirective( const {hostBindings: dirHostBindings, hostEvents: dirHostEvents} = this._visitDirective(
providerAst, dirAst, dirIndex, nodeIndex, context.elementDepth, ast.references, providerAst, dirAst, dirIndex, nodeIndex, ast.references, ast.queryMatches, usedEvents,
ast.queryMatches, usedEvents); this.staticQueryIds.get(<any>ast));
hostBindings.push(...dirHostBindings); hostBindings.push(...dirHostBindings);
hostEvents.push(...dirHostEvents); hostEvents.push(...dirHostEvents);
} else { } else {
@ -348,8 +366,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
valueType = viewEngine.QueryValueType.TemplateRef; valueType = viewEngine.QueryValueType.TemplateRef;
} }
if (valueType != null) { if (valueType != null) {
queryMatchExprs.push( queryMatchExprs.push(o.literalArr([o.literal(match.queryId), o.literal(valueType)]));
o.literalArr([o.literal(calcQueryId(match.query)), o.literal(valueType)]));
} }
}); });
ast.references.forEach((ref) => { ast.references.forEach((ref) => {
@ -361,7 +378,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
} }
if (valueType != null) { if (valueType != null) {
this.refNodeIndices[ref.name] = nodeIndex; this.refNodeIndices[ref.name] = nodeIndex;
queryMatchExprs.push(o.literalArr([o.literal(`#${ref.name}`), o.literal(valueType)])); queryMatchExprs.push(o.literalArr([o.literal(ref.name), o.literal(valueType)]));
} }
}); });
ast.outputs.forEach( ast.outputs.forEach(
@ -383,8 +400,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
private _visitDirective( private _visitDirective(
providerAst: ProviderAst, directiveAst: DirectiveAst, directiveIndex: number, providerAst: ProviderAst, directiveAst: DirectiveAst, directiveIndex: number,
elementNodeIndex: number, elementDepth: number, refs: ReferenceAst[], elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[],
queryMatches: QueryMatch[], usedEvents: Map<string, any>): { usedEvents: Map<string, any>, queryIds: StaticAndDynamicQueryIds): {
hostBindings: {value: AST, context: o.Expression}[], hostBindings: {value: AST, context: o.Expression}[],
hostEvents: {context: o.Expression, eventAst: BoundEventAst}[] hostEvents: {context: o.Expression, eventAst: BoundEventAst}[]
} { } {
@ -392,12 +409,34 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
// reserve the space in the nodeDefs array so we can add children // reserve the space in the nodeDefs array so we can add children
this.nodeDefs.push(null); this.nodeDefs.push(null);
directiveAst.directive.viewQueries.forEach((query, queryIndex) => {
// Note: queries start with id 1 so we can use the number in a Bloom filter!
const queryId = queryIndex + 1;
const bindingType =
query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All;
let flags = viewEngine.NodeFlags.HasViewQuery;
if (queryIds.staticQueryIds.has(queryId)) {
flags |= viewEngine.NodeFlags.HasStaticQuery;
} else {
flags |= viewEngine.NodeFlags.HasDynamicQuery;
}
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
o.literal(flags), o.literal(queryId),
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
]));
});
directiveAst.directive.queries.forEach((query, queryIndex) => { directiveAst.directive.queries.forEach((query, queryIndex) => {
const queryId: QueryId = {elementDepth, directiveIndex, queryIndex}; let flags = viewEngine.NodeFlags.HasContentQuery;
const queryId = directiveAst.contentQueryStartId + queryIndex;
if (queryIds.staticQueryIds.has(queryId)) {
flags |= viewEngine.NodeFlags.HasStaticQuery;
} else {
flags |= viewEngine.NodeFlags.HasDynamicQuery;
}
const bindingType = const bindingType =
query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All; query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All;
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([ this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
o.literal(viewEngine.NodeFlags.HasContentQuery), o.literal(calcQueryId(queryId)), o.literal(flags), o.literal(queryId),
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))]) new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
])); ]));
}); });
@ -414,8 +453,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
refs.forEach((ref) => { refs.forEach((ref) => {
if (ref.value && tokenReference(ref.value) === tokenReference(providerAst.token)) { if (ref.value && tokenReference(ref.value) === tokenReference(providerAst.token)) {
this.refNodeIndices[ref.name] = nodeIndex; this.refNodeIndices[ref.name] = nodeIndex;
queryMatchExprs.push(o.literalArr( queryMatchExprs.push(
[o.literal(`#${ref.name}`), o.literal(viewEngine.QueryValueType.Provider)])); o.literalArr([o.literal(ref.name), o.literal(viewEngine.QueryValueType.Provider)]));
} }
}); });
@ -505,6 +544,9 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
if (!providerAst.eager) { if (!providerAst.eager) {
flags |= viewEngine.NodeFlags.LazyProvider; flags |= viewEngine.NodeFlags.LazyProvider;
} }
if (providerAst.providerType === ProviderAstType.PrivateService) {
flags |= viewEngine.NodeFlags.PrivateProvider;
}
providerAst.lifecycleHooks.forEach((lifecycleHook) => { providerAst.lifecycleHooks.forEach((lifecycleHook) => {
// for regular providers, we only support ngOnDestroy // for regular providers, we only support ngOnDestroy
if (lifecycleHook === LifecycleHooks.OnDestroy || if (lifecycleHook === LifecycleHooks.OnDestroy ||
@ -518,7 +560,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
queryMatches.forEach((match) => { queryMatches.forEach((match) => {
if (tokenReference(match.value) === tokenReference(providerAst.token)) { if (tokenReference(match.value) === tokenReference(providerAst.token)) {
queryMatchExprs.push(o.literalArr( queryMatchExprs.push(o.literalArr(
[o.literal(calcQueryId(match.query)), o.literal(viewEngine.QueryValueType.Provider)])); [o.literal(match.queryId), o.literal(viewEngine.QueryValueType.Provider)]));
} }
}); });
const {providerExpr, providerType, depsExpr} = providerDef(providerAst); const {providerExpr, providerType, depsExpr} = providerDef(providerAst);
@ -680,18 +722,15 @@ function multiProviderDef(providers: CompileProviderMetadata[]):
const allDepDefs: o.Expression[] = []; const allDepDefs: o.Expression[] = [];
const allParams: o.FnParam[] = []; const allParams: o.FnParam[] = [];
const exprs = providers.map((provider, providerIndex) => { const exprs = providers.map((provider, providerIndex) => {
const depExprs = provider.deps.map((dep, depIndex) => {
const paramName = `p${providerIndex}_${depIndex}`;
allParams.push(new o.FnParam(paramName, o.DYNAMIC_TYPE));
allDepDefs.push(depDef(dep));
return o.variable(paramName);
});
let expr: o.Expression; let expr: o.Expression;
if (provider.useClass) { if (provider.useClass) {
const depExprs = convertDeps(providerIndex, provider.deps || provider.useClass.diDeps);
expr = o.importExpr(provider.useClass).instantiate(depExprs); expr = o.importExpr(provider.useClass).instantiate(depExprs);
} else if (provider.useFactory) { } else if (provider.useFactory) {
const depExprs = convertDeps(providerIndex, provider.deps || provider.useFactory.diDeps);
expr = o.importExpr(provider.useFactory).callFn(depExprs); expr = o.importExpr(provider.useFactory).callFn(depExprs);
} else if (provider.useExisting) { } else if (provider.useExisting) {
const depExprs = convertDeps(providerIndex, [{token: provider.useExisting}]);
expr = depExprs[0]; expr = depExprs[0];
} else { } else {
expr = convertValueToOutputAst(provider.useValue); expr = convertValueToOutputAst(provider.useValue);
@ -704,6 +743,15 @@ function multiProviderDef(providers: CompileProviderMetadata[]):
providerType: viewEngine.ProviderType.Factory, providerType: viewEngine.ProviderType.Factory,
depsExpr: o.literalArr(allDepDefs) depsExpr: o.literalArr(allDepDefs)
}; };
function convertDeps(providerIndex: number, deps: CompileDiDependencyMetadata[]) {
return deps.map((dep, depIndex) => {
const paramName = `p${providerIndex}_${depIndex}`;
allParams.push(new o.FnParam(paramName, o.DYNAMIC_TYPE));
allDepDefs.push(depDef(dep));
return o.variable(paramName);
});
}
} }
function singleProviderDef(providerMeta: CompileProviderMetadata): function singleProviderDef(providerMeta: CompileProviderMetadata):
@ -765,15 +813,6 @@ function needsAdditionalRootNode(ast: TemplateAst): boolean {
return ast instanceof NgContentAst; return ast instanceof NgContentAst;
} }
function calcQueryId(queryId: QueryId): string {
if (queryId.directiveIndex == null) {
// view query
return `v${queryId.queryIndex}`;
} else {
return `c${queryId.elementDepth}_${queryId.directiveIndex}_${queryId.queryIndex}`;
}
}
function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): number { function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): number {
let nodeFlag = viewEngine.NodeFlags.None; let nodeFlag = viewEngine.NodeFlags.None;
switch (lifecycleHook) { switch (lifecycleHook) {
@ -873,3 +912,65 @@ function callCheckStmt(nodeIndex: number, exprs: o.Expression[]): o.Expression {
function callUnwrapValue(expr: o.Expression): o.Expression { function callUnwrapValue(expr: o.Expression): o.Expression {
return o.importExpr(createIdentifier(Identifiers.unwrapValue)).callFn([expr]); return o.importExpr(createIdentifier(Identifiers.unwrapValue)).callFn([expr]);
} }
interface StaticAndDynamicQueryIds {
staticQueryIds: Set<number>;
dynamicQueryIds: Set<number>;
}
function findStaticQueryIds(
nodes: TemplateAst[], result = new Map<TemplateAst, StaticAndDynamicQueryIds>()):
Map<TemplateAst, StaticAndDynamicQueryIds> {
nodes.forEach((node) => {
const staticQueryIds = new Set<number>();
const dynamicQueryIds = new Set<number>();
let queryMatches: QueryMatch[];
if (node instanceof ElementAst) {
findStaticQueryIds(node.children, result);
node.children.forEach((child) => {
const childData = result.get(child);
childData.staticQueryIds.forEach(queryId => staticQueryIds.add(queryId));
childData.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId));
});
queryMatches = node.queryMatches;
} else if (node instanceof EmbeddedTemplateAst) {
findStaticQueryIds(node.children, result);
node.children.forEach((child) => {
const childData = result.get(child);
childData.staticQueryIds.forEach(queryId => dynamicQueryIds.add(queryId));
childData.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId));
});
queryMatches = node.queryMatches;
}
if (queryMatches) {
queryMatches.forEach((match) => staticQueryIds.add(match.queryId));
}
dynamicQueryIds.forEach(queryId => staticQueryIds.delete(queryId));
result.set(node, {staticQueryIds, dynamicQueryIds});
});
return result;
}
function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst {
const componentDirMeta = directives.find(dirAst => dirAst.directive.isComponent);
if (componentDirMeta) {
const entryComponentFactories = componentDirMeta.directive.entryComponents.map(
(entryComponent) => o.importExpr({reference: entryComponent.componentFactory}));
const cfrExpr = o.importExpr(createIdentifier(Identifiers.CodegenComponentFactoryResolver))
.instantiate([o.literalArr(entryComponentFactories)]);
const token = createIdentifierToken(Identifiers.ComponentFactoryResolver);
const classMeta: CompileTypeMetadata = {
diDeps: [
{isValue: true, value: o.literalArr(entryComponentFactories)},
{token: token, isSkipSelf: true, isOptional: true}
],
lifecycleHooks: [],
reference: resolveIdentifier(Identifiers.CodegenComponentFactoryResolver)
};
return new ProviderAst(
token, false, true, [{token, multi: false, useClass: classMeta}],
ProviderAstType.PrivateService, [], componentDirMeta.sourceSpan);
}
return null;
}

View File

@ -155,7 +155,7 @@ export function main() {
expectVisitedNode( expectVisitedNode(
new class extends new class extends
NullVisitor{visitDirective(ast: DirectiveAst, context: any): any{return ast;}}, NullVisitor{visitDirective(ast: DirectiveAst, context: any): any{return ast;}},
new DirectiveAst(null, [], [], [], null)); new DirectiveAst(null, [], [], [], 0, null));
}); });
it('should visit DirectiveAst', () => { it('should visit DirectiveAst', () => {
@ -178,7 +178,7 @@ export function main() {
new BoundEventAst('foo', 'bar', 'goo', null, null), new BoundEventAst('foo', 'bar', 'goo', null, null),
new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null), new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null),
new AttrAst('foo', 'bar', null), new BoundTextAst(null, 0, 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) new BoundDirectivePropertyAst('foo', 'bar', null, null)
]; ];
const result = templateVisitAll(visitor, nodes, null); const result = templateVisitAll(visitor, nodes, null);

View File

@ -10,39 +10,39 @@ import {isDevMode} from '../application_ref';
import {SecurityContext} from '../security'; 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 {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( export function anchorDef(
flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef { ngContentIndex: number, childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef {
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {}; const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
if (matchedQueries) {
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
}
// skip the call to sliceErrorStack itself + the call to this function. // skip the call to sliceErrorStack itself + the call to this function.
const source = isDevMode() ? sliceErrorStack(2, 3) : ''; const source = isDevMode() ? sliceErrorStack(2, 3) : '';
const template = templateFactory ? resolveViewDefinition(templateFactory) : null; const template = templateFactory ? resolveViewDefinition(templateFactory) : null;
return { return {
type: NodeType.Element, type: NodeType.Element,
// will bet set by the view definition // will bet set by the view definition
index: undefined, index: undefined,
reverseChildIndex: undefined, reverseChildIndex: undefined,
parent: undefined, parent: undefined,
childFlags: undefined, renderParent: undefined,
childMatchedQueries: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, disposableIndex: undefined,
// regular values // regular values
flags, flags,
matchedQueries: matchedQueryDefs, ngContentIndex, childCount, childFlags: 0,
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount,
bindings: [], bindings: [],
disposableCount: 0, disposableCount: 0,
element: { element: {
name: undefined, name: undefined,
attrs: undefined, attrs: undefined,
outputs: [], template, outputs: [], template, source,
// will bet set by the view definition // will bet set by the view definition
providerIndices: undefined, source, component: undefined,
publicProviders: undefined,
allProviders: undefined,
}, },
provider: undefined, provider: undefined,
text: undefined, text: undefined,
@ -53,18 +53,16 @@ export function anchorDef(
} }
export function elementDef( export function elementDef(
flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
childCount: number, name: string, fixedAttrs: {[name: string]: string} = {}, ngContentIndex: number, childCount: number, name: string,
fixedAttrs: {[name: string]: string} = {},
bindings?: bindings?:
([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] | ([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] |
[BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[], [BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[],
outputs?: (string | [string, string])[]): NodeDef { outputs?: (string | [string, string])[]): NodeDef {
// skip the call to sliceErrorStack itself + the call to this function. // skip the call to sliceErrorStack itself + the call to this function.
const source = isDevMode() ? sliceErrorStack(2, 3) : ''; const source = isDevMode() ? sliceErrorStack(2, 3) : '';
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {}; const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
if (matchedQueries) {
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
}
bindings = bindings || []; bindings = bindings || [];
const bindingDefs: BindingDef[] = new Array(bindings.length); const bindingDefs: BindingDef[] = new Array(bindings.length);
for (let i = 0; i < bindings.length; i++) { for (let i = 0; i < bindings.length; i++) {
@ -104,22 +102,24 @@ export function elementDef(
index: undefined, index: undefined,
reverseChildIndex: undefined, reverseChildIndex: undefined,
parent: undefined, parent: undefined,
childFlags: undefined, renderParent: undefined,
childMatchedQueries: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, disposableIndex: undefined,
// regular values // regular values
flags, flags,
matchedQueries: matchedQueryDefs, ngContentIndex, childCount, childFlags: 0,
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount,
bindings: bindingDefs, bindings: bindingDefs,
disposableCount: outputDefs.length, disposableCount: outputDefs.length,
element: { element: {
name, name,
attrs: fixedAttrs, attrs: fixedAttrs,
outputs: outputDefs, outputs: outputDefs, source,
template: undefined, template: undefined,
// will bet set by the view definition // will bet set by the view definition
providerIndices: undefined, source, component: undefined,
publicProviders: undefined,
allProviders: undefined,
}, },
provider: undefined, provider: undefined,
text: undefined, text: undefined,
@ -135,8 +135,6 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El
const renderer = view.root.renderer; const renderer = view.root.renderer;
let el: any; let el: any;
if (view.parent || !rootSelectorOrNode) { if (view.parent || !rootSelectorOrNode) {
const parentNode =
def.parent != null ? asElementData(view, def.parent).renderElement : renderHost;
if (elDef.name) { if (elDef.name) {
// TODO(vicb): move the namespace to the node definition // TODO(vicb): move the namespace to the node definition
const nsAndName = splitNamespace(elDef.name); const nsAndName = splitNamespace(elDef.name);
@ -144,8 +142,9 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El
} else { } else {
el = renderer.createComment(''); el = renderer.createComment('');
} }
if (parentNode) { const parentEl = getParentRenderElement(view, renderHost, def);
renderer.appendChild(parentNode, el); if (parentEl) {
renderer.appendChild(parentEl, el);
} }
} else { } else {
el = renderer.selectRootElement(rootSelectorOrNode); el = renderer.selectRootElement(rootSelectorOrNode);

View File

@ -7,7 +7,7 @@
*/ */
import {NodeDef, NodeType, ViewData, asElementData} from './types'; 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 { export function ngContentDef(ngContentIndex: number, index: number): NodeDef {
return { return {
@ -16,13 +16,16 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef {
index: undefined, index: undefined,
reverseChildIndex: undefined, reverseChildIndex: undefined,
parent: undefined, parent: undefined,
childFlags: undefined, renderParent: undefined,
childMatchedQueries: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, disposableIndex: undefined,
// regular values // regular values
flags: 0, flags: 0,
matchedQueries: {}, ngContentIndex, childFlags: 0,
childMatchedQueries: 0,
matchedQueries: {},
matchedQueryIds: 0,
references: {}, ngContentIndex,
childCount: 0, childCount: 0,
bindings: [], bindings: [],
disposableCount: 0, disposableCount: 0,
@ -36,11 +39,7 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef {
} }
export function appendNgContent(view: ViewData, renderHost: any, def: NodeDef) { export function appendNgContent(view: ViewData, renderHost: any, def: NodeDef) {
if (def.ngContentIndex != null) { const parentEl = getParentRenderElement(view, renderHost, def);
// Do nothing if we are reprojected!
return;
}
const parentEl = def.parent != null ? asElementData(view, def.parent).renderElement : renderHost;
if (!parentEl) { if (!parentEl) {
// Nothing to do if there is no parent element. // Nothing to do if there is no parent element.
return; return;

View File

@ -15,7 +15,7 @@ import * as v1renderer from '../render/api';
import {createChangeDetectorRef, createInjector, createTemplateRef, createViewContainerRef} from './refs'; import {createChangeDetectorRef, createInjector, createTemplateRef, createViewContainerRef} from './refs';
import {BindingDef, BindingType, DepDef, DepFlags, DirectiveOutputDef, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderType, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types'; import {BindingDef, BindingType, DepDef, DepFlags, DirectiveOutputDef, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderType, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types';
import {checkAndUpdateBinding, dispatchEvent, isComponentView, tokenKey, viewParentElIndex} from './util'; import {checkAndUpdateBinding, dispatchEvent, filterQueryId, isComponentView, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util';
const RendererV1TokenKey = tokenKey(v1renderer.Renderer); const RendererV1TokenKey = tokenKey(v1renderer.Renderer);
const ElementRefTokenKey = tokenKey(ElementRef); const ElementRefTokenKey = tokenKey(ElementRef);
@ -27,8 +27,8 @@ const InjectorRefTokenKey = tokenKey(Injector);
const NOT_CREATED = new Object(); const NOT_CREATED = new Object();
export function directiveDef( export function directiveDef(
flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor: any, flags: NodeFlags, matchedQueries: [string | number, QueryValueType][], childCount: number,
deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]}, ctor: any, deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef { outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef {
const bindings: BindingDef[] = []; const bindings: BindingDef[] = [];
if (props) { if (props) {
@ -58,20 +58,17 @@ export function pipeDef(flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | an
} }
export function providerDef( export function providerDef(
flags: NodeFlags, matchedQueries: [string, QueryValueType][], type: ProviderType, token: any, flags: NodeFlags, matchedQueries: [string | number, QueryValueType][], type: ProviderType,
value: any, deps: ([DepFlags, any] | any)[]): NodeDef { token: any, value: any, deps: ([DepFlags, any] | any)[]): NodeDef {
return _def(NodeType.Provider, flags, matchedQueries, 0, type, token, value, deps); return _def(NodeType.Provider, flags, matchedQueries, 0, type, token, value, deps);
} }
export function _def( export function _def(
type: NodeType, flags: NodeFlags, matchedQueries: [string, QueryValueType][], type: NodeType, flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
childCount: number, providerType: ProviderType, token: any, value: any, childCount: number, providerType: ProviderType, token: any, value: any,
deps: ([DepFlags, any] | any)[], bindings?: BindingDef[], outputs?: DirectiveOutputDef[], deps: ([DepFlags, any] | any)[], bindings?: BindingDef[], outputs?: DirectiveOutputDef[],
component?: () => ViewDefinition): NodeDef { component?: () => ViewDefinition): NodeDef {
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {}; const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
if (matchedQueries) {
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
}
if (!outputs) { if (!outputs) {
outputs = []; outputs = [];
} }
@ -100,13 +97,13 @@ export function _def(
index: undefined, index: undefined,
reverseChildIndex: undefined, reverseChildIndex: undefined,
parent: undefined, parent: undefined,
childFlags: undefined, renderParent: undefined,
childMatchedQueries: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, disposableIndex: undefined,
// regular values // regular values
flags, flags,
matchedQueries: matchedQueryDefs, childFlags: 0,
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references,
ngContentIndex: undefined, childCount, bindings, ngContentIndex: undefined, childCount, bindings,
disposableCount: outputs.length, disposableCount: outputs.length,
element: undefined, element: undefined,
@ -133,21 +130,26 @@ export function createPipeInstance(view: ViewData, def: NodeDef): any {
while (compView.parent && !isComponentView(compView)) { while (compView.parent && !isComponentView(compView)) {
compView = compView.parent; compView = compView.parent;
} }
// pipes can see the private services of the component
const allowPrivateServices = true;
// pipes are always eager and classes! // pipes are always eager and classes!
return createClass( return createClass(
compView.parent, compView.parentIndex, viewParentElIndex(compView), def.provider.value, compView.parent, viewParentEl(compView), allowPrivateServices, def.provider.value,
def.provider.deps); def.provider.deps);
} }
export function createDirectiveInstance(view: ViewData, def: NodeDef): any { export function createDirectiveInstance(view: ViewData, def: NodeDef): any {
// components can see other private services, other directives can't.
const allowPrivateServices = (def.flags & NodeFlags.HasComponent) > 0;
const providerDef = def.provider; const providerDef = def.provider;
// directives are always eager and classes! // directives are always eager and classes!
const instance = createClass(view, def.index, def.parent, def.provider.value, def.provider.deps); const instance =
createClass(view, def.parent, allowPrivateServices, def.provider.value, def.provider.deps);
if (providerDef.outputs.length) { if (providerDef.outputs.length) {
for (let i = 0; i < providerDef.outputs.length; i++) { for (let i = 0; i < providerDef.outputs.length; i++) {
const output = providerDef.outputs[i]; const output = providerDef.outputs[i];
const subscription = instance[output.propName].subscribe( const subscription = instance[output.propName].subscribe(
eventHandlerClosure(view, def.parent, output.eventName)); eventHandlerClosure(view, def.parent.index, output.eventName));
view.disposables[def.disposableIndex + i] = subscription.unsubscribe.bind(subscription); view.disposables[def.disposableIndex + i] = subscription.unsubscribe.bind(subscription);
} }
} }
@ -217,17 +219,21 @@ export function checkAndUpdateDirectiveDynamic(view: ViewData, def: NodeDef, val
} }
function _createProviderInstance(view: ViewData, def: NodeDef): any { function _createProviderInstance(view: ViewData, def: NodeDef): any {
// private services can see other private services
const allowPrivateServices = (def.flags & NodeFlags.PrivateProvider) > 0;
const providerDef = def.provider; const providerDef = def.provider;
let injectable: any; let injectable: any;
switch (providerDef.type) { switch (providerDef.type) {
case ProviderType.Class: case ProviderType.Class:
injectable = createClass(view, def.index, def.parent, providerDef.value, providerDef.deps); injectable =
createClass(view, def.parent, allowPrivateServices, providerDef.value, providerDef.deps);
break; break;
case ProviderType.Factory: case ProviderType.Factory:
injectable = callFactory(view, def.index, def.parent, providerDef.value, providerDef.deps); injectable =
callFactory(view, def.parent, allowPrivateServices, providerDef.value, providerDef.deps);
break; break;
case ProviderType.UseExisting: case ProviderType.UseExisting:
injectable = resolveDep(view, def.index, def.parent, providerDef.deps[0]); injectable = resolveDep(view, def.parent, allowPrivateServices, providerDef.deps[0]);
break; break;
case ProviderType.Value: case ProviderType.Value:
injectable = providerDef.value; injectable = providerDef.value;
@ -237,7 +243,7 @@ function _createProviderInstance(view: ViewData, def: NodeDef): any {
} }
function createClass( function createClass(
view: ViewData, requestorNodeIndex: number, elIndex: number, ctor: any, deps: DepDef[]): any { view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, ctor: any, deps: DepDef[]): any {
const len = deps.length; const len = deps.length;
let injectable: any; let injectable: any;
switch (len) { switch (len) {
@ -245,23 +251,23 @@ function createClass(
injectable = new ctor(); injectable = new ctor();
break; break;
case 1: case 1:
injectable = new ctor(resolveDep(view, requestorNodeIndex, elIndex, deps[0])); injectable = new ctor(resolveDep(view, elDef, allowPrivateServices, deps[0]));
break; break;
case 2: case 2:
injectable = new ctor( injectable = new ctor(
resolveDep(view, requestorNodeIndex, elIndex, deps[0]), resolveDep(view, elDef, allowPrivateServices, deps[0]),
resolveDep(view, requestorNodeIndex, elIndex, deps[1])); resolveDep(view, elDef, allowPrivateServices, deps[1]));
break; break;
case 3: case 3:
injectable = new ctor( injectable = new ctor(
resolveDep(view, requestorNodeIndex, elIndex, deps[0]), resolveDep(view, elDef, allowPrivateServices, deps[0]),
resolveDep(view, requestorNodeIndex, elIndex, deps[1]), resolveDep(view, elDef, allowPrivateServices, deps[1]),
resolveDep(view, requestorNodeIndex, elIndex, deps[2])); resolveDep(view, elDef, allowPrivateServices, deps[2]));
break; break;
default: default:
const depValues = new Array(len); const depValues = new Array(len);
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
depValues[i] = resolveDep(view, requestorNodeIndex, elIndex, deps[i]); depValues[i] = resolveDep(view, elDef, allowPrivateServices, deps[i]);
} }
injectable = new ctor(...depValues); injectable = new ctor(...depValues);
} }
@ -269,7 +275,7 @@ function createClass(
} }
function callFactory( function callFactory(
view: ViewData, requestorNodeIndex: number, elIndex: number, factory: any, view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, factory: any,
deps: DepDef[]): any { deps: DepDef[]): any {
const len = deps.length; const len = deps.length;
let injectable: any; let injectable: any;
@ -278,23 +284,23 @@ function callFactory(
injectable = factory(); injectable = factory();
break; break;
case 1: case 1:
injectable = factory(resolveDep(view, requestorNodeIndex, elIndex, deps[0])); injectable = factory(resolveDep(view, elDef, allowPrivateServices, deps[0]));
break; break;
case 2: case 2:
injectable = factory( injectable = factory(
resolveDep(view, requestorNodeIndex, elIndex, deps[0]), resolveDep(view, elDef, allowPrivateServices, deps[0]),
resolveDep(view, requestorNodeIndex, elIndex, deps[1])); resolveDep(view, elDef, allowPrivateServices, deps[1]));
break; break;
case 3: case 3:
injectable = factory( injectable = factory(
resolveDep(view, requestorNodeIndex, elIndex, deps[0]), resolveDep(view, elDef, allowPrivateServices, deps[0]),
resolveDep(view, requestorNodeIndex, elIndex, deps[1]), resolveDep(view, elDef, allowPrivateServices, deps[1]),
resolveDep(view, requestorNodeIndex, elIndex, deps[2])); resolveDep(view, elDef, allowPrivateServices, deps[2]));
break; break;
default: default:
const depValues = Array(len); const depValues = Array(len);
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
depValues[i] = resolveDep(view, requestorNodeIndex, elIndex, deps[i]); depValues[i] = resolveDep(view, elDef, allowPrivateServices, deps[i]);
} }
injectable = factory(...depValues); injectable = factory(...depValues);
} }
@ -302,7 +308,7 @@ function callFactory(
} }
export function resolveDep( export function resolveDep(
view: ViewData, requestNodeIndex: number, elIndex: number, depDef: DepDef, view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef,
notFoundValue = Injector.THROW_IF_NOT_FOUND): any { notFoundValue = Injector.THROW_IF_NOT_FOUND): any {
if (depDef.flags & DepFlags.Value) { if (depDef.flags & DepFlags.Value) {
return depDef.token; return depDef.token;
@ -314,59 +320,64 @@ export function resolveDep(
const tokenKey = depDef.tokenKey; const tokenKey = depDef.tokenKey;
if (depDef.flags & DepFlags.SkipSelf) { if (depDef.flags & DepFlags.SkipSelf) {
requestNodeIndex = null; allowPrivateServices = false;
elIndex = view.def.nodes[elIndex].parent; elDef = elDef.parent;
while (elIndex == null && view) {
elIndex = viewParentElIndex(view);
view = view.parent;
}
} }
while (view) { while (view) {
const elDef = view.def.nodes[elIndex]; if (elDef) {
switch (tokenKey) { switch (tokenKey) {
case RendererV1TokenKey: { case RendererV1TokenKey: {
let compView = view; let compView = view;
while (compView && !isComponentView(compView)) { while (compView && !isComponentView(compView)) {
compView = compView.parent; compView = compView.parent;
} }
const rootRenderer: v1renderer.RootRenderer = const rootRenderer: v1renderer.RootRenderer =
view.root.injector.get(v1renderer.RootRenderer); view.root.injector.get(v1renderer.RootRenderer);
// Note: Don't fill in the styles as they have been installed already! // Note: Don't fill in the styles as they have been installed already!
return rootRenderer.renderComponent(new v1renderer.RenderComponentType( return rootRenderer.renderComponent(new v1renderer.RenderComponentType(
view.def.component.id, '', 0, view.def.component.encapsulation, [], {})); view.def.component.id, '', 0, view.def.component.encapsulation, [], {}));
}
case ElementRefTokenKey:
return new ElementRef(asElementData(view, elDef.index).renderElement);
case ViewContainerRefTokenKey:
return createViewContainerRef(view, elDef);
case TemplateRefTokenKey: {
if (elDef.element.template) {
return createTemplateRef(view, elDef);
}
break;
}
case ChangeDetectorRefTokenKey: {
let cdView: ViewData;
if (allowPrivateServices) {
cdView = asProviderData(view, elDef.element.component.index).componentView;
} else {
cdView = view;
while (cdView.parent && !isComponentView(cdView)) {
cdView = cdView.parent;
}
}
return createChangeDetectorRef(cdView);
}
case InjectorRefTokenKey:
return createInjector(view, elDef);
default:
const providerDef =
(allowPrivateServices ? elDef.element.allProviders :
elDef.element.publicProviders)[tokenKey];
if (providerDef) {
const providerData = asProviderData(view, providerDef.index);
if (providerData.instance === NOT_CREATED) {
providerData.instance = _createProviderInstance(view, providerDef);
}
return providerData.instance;
}
} }
case ElementRefTokenKey:
return new ElementRef(asElementData(view, elIndex).renderElement);
case ViewContainerRefTokenKey:
return createViewContainerRef(view, elIndex);
case TemplateRefTokenKey:
return createTemplateRef(view, elDef);
case ChangeDetectorRefTokenKey:
let cdView = view;
// If we are still checking dependencies on the initial element...
if (requestNodeIndex != null) {
const requestorNodeDef = view.def.nodes[requestNodeIndex];
if (requestorNodeDef.flags & NodeFlags.HasComponent) {
cdView = asProviderData(view, requestNodeIndex).componentView;
}
}
return createChangeDetectorRef(cdView);
case InjectorRefTokenKey:
return createInjector(view, elIndex);
default:
const providerIndex = elDef.element.providerIndices[tokenKey];
if (providerIndex != null) {
const providerData = asProviderData(view, providerIndex);
if (providerData.instance === NOT_CREATED) {
providerData.instance = _createProviderInstance(view, view.def.nodes[providerIndex]);
}
return providerData.instance;
}
} }
requestNodeIndex = null; allowPrivateServices = isComponentView(view);
elIndex = viewParentElIndex(view); elDef = viewParentEl(view);
view = view.parent; view = view.parent;
} }
return startView.root.injector.get(depDef.token, notFoundValue); return startView.root.injector.get(depDef.token, notFoundValue);

View File

@ -40,13 +40,16 @@ function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]):
index: undefined, index: undefined,
reverseChildIndex: undefined, reverseChildIndex: undefined,
parent: undefined, parent: undefined,
childFlags: undefined, renderParent: undefined,
childMatchedQueries: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, disposableIndex: undefined,
// regular values // regular values
flags: 0, flags: 0,
childFlags: 0,
childMatchedQueries: 0,
matchedQueries: {}, matchedQueries: {},
matchedQueryIds: 0,
references: {},
ngContentIndex: undefined, ngContentIndex: undefined,
childCount: 0, bindings, childCount: 0, bindings,
disposableCount: 0, disposableCount: 0,

View File

@ -13,10 +13,10 @@ import {ViewContainerRef} from '../linker/view_container_ref';
import {createTemplateRef, createViewContainerRef} from './refs'; import {createTemplateRef, createViewContainerRef} from './refs';
import {NodeDef, NodeFlags, NodeType, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, asElementData, asProviderData, asQueryList} from './types'; 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( export function queryDef(
flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef { flags: NodeFlags, id: number, bindings: {[propName: string]: QueryBindingType}): NodeDef {
let bindingDefs: QueryBindingDef[] = []; let bindingDefs: QueryBindingDef[] = [];
for (let propName in bindings) { for (let propName in bindings) {
const bindingType = bindings[propName]; const bindingType = bindings[propName];
@ -29,14 +29,17 @@ export function queryDef(
index: undefined, index: undefined,
reverseChildIndex: undefined, reverseChildIndex: undefined,
parent: undefined, parent: undefined,
childFlags: undefined, renderParent: undefined,
childMatchedQueries: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, disposableIndex: undefined,
// regular values // regular values
flags, flags,
childFlags: 0,
childMatchedQueries: 0,
ngContentIndex: undefined, ngContentIndex: undefined,
matchedQueries: {}, matchedQueries: {},
matchedQueryIds: 0,
references: {},
childCount: 0, childCount: 0,
bindings: [], bindings: [],
disposableCount: 0, disposableCount: 0,
@ -44,7 +47,7 @@ export function queryDef(
provider: undefined, provider: undefined,
text: undefined, text: undefined,
pureExpression: undefined, pureExpression: undefined,
query: {id, bindings: bindingDefs}, query: {id, filterId: filterQueryId(id), bindings: bindingDefs},
ngContent: undefined ngContent: undefined
}; };
} }
@ -53,26 +56,40 @@ export function createQuery(): QueryList<any> {
return new QueryList(); return new QueryList();
} }
export function dirtyParentQuery(queryId: string, view: ViewData) { export function dirtyParentQueries(view: ViewData) {
let elIndex = viewParentElIndex(view); const queryIds = view.def.nodeMatchedQueries;
view = view.parent; while (view.parent && isEmbeddedView(view)) {
let queryIdx: number; let tplDef = view.parentNodeDef;
while (view) { view = view.parent;
if (elIndex != null) { // content queries
const elementDef = view.def.nodes[elIndex]; const end = tplDef.index + tplDef.childCount;
queryIdx = elementDef.element.providerIndices[queryId]; for (let i = 0; i <= end; i++) {
if (queryIdx != null) { const nodeDef = view.def.nodes[i];
break; 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( // view queries
`Illegal State: Tried to dirty parent query ${queryId} but the query could not be found!`); 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) { export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) {
@ -80,76 +97,80 @@ export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) {
if (!queryList.dirty) { if (!queryList.dirty) {
return; return;
} }
const queryId = nodeDef.query.id; const providerDef = nodeDef.parent;
const providerDef = view.def.nodes[nodeDef.parent];
const providerData = asProviderData(view, providerDef.index); const providerData = asProviderData(view, providerDef.index);
let newValues: any[]; let newValues: any[];
if (nodeDef.flags & NodeFlags.HasContentQuery) { if (nodeDef.flags & NodeFlags.HasContentQuery) {
const elementDef = view.def.nodes[providerDef.parent]; const elementDef = providerDef.parent;
newValues = calcQueryValues( 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) { } else if (nodeDef.flags & NodeFlags.HasViewQuery) {
const compView = providerData.componentView; 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); queryList.reset(newValues);
let boundValue: any;
const bindings = nodeDef.query.bindings; const bindings = nodeDef.query.bindings;
let notify = false;
for (let i = 0; i < bindings.length; i++) { for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i]; const binding = bindings[i];
let boundValue: any;
switch (binding.bindingType) { switch (binding.bindingType) {
case QueryBindingType.First: case QueryBindingType.First:
boundValue = queryList.first; boundValue = queryList.first;
break; break;
case QueryBindingType.All: case QueryBindingType.All:
boundValue = queryList; boundValue = queryList;
notify = true;
break; break;
} }
providerData.instance[binding.propName] = boundValue; providerData.instance[binding.propName] = boundValue;
} }
if (notify) {
queryList.notifyOnChanges();
}
} }
function calcQueryValues( function calcQueryValues(
view: ViewData, startIndex: number, endIndex: number, queryId: string, values: any[]): any[] { view: ViewData, startIndex: number, endIndex: number, queryDef: QueryDef,
const len = view.def.nodes.length; values: any[]): any[] {
for (let i = startIndex; i <= endIndex; i++) { for (let i = startIndex; i <= endIndex; i++) {
const nodeDef = view.def.nodes[i]; const nodeDef = view.def.nodes[i];
const value = getQueryValue(view, nodeDef, queryId); const valueType = nodeDef.matchedQueries[queryDef.id];
if (value != null) { if (valueType != null) {
// a match values.push(getQueryValue(view, nodeDef, valueType));
values.push(value);
} }
if (nodeDef.flags & NodeFlags.HasEmbeddedViews && if (nodeDef.type === NodeType.Element && nodeDef.element.template &&
queryId in nodeDef.element.template.nodeMatchedQueries) { (nodeDef.element.template.nodeMatchedQueries & queryDef.filterId) === queryDef.filterId) {
// check embedded views that were attached at the place of their template. // check embedded views that were attached at the place of their template.
const elementData = asElementData(view, i); const elementData = asElementData(view, i);
const embeddedViews = elementData.embeddedViews; const embeddedViews = elementData.embeddedViews;
for (let k = 0; k < embeddedViews.length; k++) { if (embeddedViews) {
const embeddedView = embeddedViews[k]; for (let k = 0; k < embeddedViews.length; k++) {
const dvc = declaredViewContainer(embeddedView); const embeddedView = embeddedViews[k];
if (dvc && dvc === elementData) { const dvc = declaredViewContainer(embeddedView);
calcQueryValues(embeddedView, 0, embeddedView.def.nodes.length - 1, queryId, values); if (dvc && dvc === elementData) {
calcQueryValues(embeddedView, 0, embeddedView.def.nodes.length - 1, queryDef, values);
}
} }
} }
const projectedViews = elementData.projectedViews; const projectedViews = elementData.projectedViews;
if (projectedViews) { if (projectedViews) {
for (let k = 0; k < projectedViews.length; k++) { for (let k = 0; k < projectedViews.length; k++) {
const projectedView = projectedViews[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 ((nodeDef.childMatchedQueries & queryDef.filterId) !== queryDef.filterId) {
// If don't check descendants, skip the children. // if no child matches the query, skip the children.
// Or: no child matches the query, then skip the children as well.
i += nodeDef.childCount; i += nodeDef.childCount;
} }
} }
return values; return values;
} }
export function getQueryValue(view: ViewData, nodeDef: NodeDef, queryId: string): any { export function getQueryValue(
const queryValueType = <QueryValueType>nodeDef.matchedQueries[queryId]; view: ViewData, nodeDef: NodeDef, queryValueType: QueryValueType): any {
if (queryValueType != null) { if (queryValueType != null) {
// a match // a match
let value: any; let value: any;
@ -164,7 +185,7 @@ export function getQueryValue(view: ViewData, nodeDef: NodeDef, queryId: string)
value = createTemplateRef(view, nodeDef); value = createTemplateRef(view, nodeDef);
break; break;
case QueryValueType.ViewContainerRef: case QueryValueType.ViewContainerRef:
value = createViewContainerRef(view, nodeDef.index); value = createViewContainerRef(view, nodeDef);
break; break;
case QueryValueType.Provider: case QueryValueType.Provider:
value = asProviderData(view, nodeDef.index).instance; value = asProviderData(view, nodeDef.index).instance;

View File

@ -16,7 +16,7 @@ import {EmbeddedViewRef, ViewRef} from '../linker/view_ref';
import {Type} from '../type'; 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 {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(); const EMPTY_CONTEXT = new Object();
@ -45,18 +45,7 @@ class ComponentFactory_ implements ComponentFactory<any> {
injector: Injector, projectableNodes: any[][] = null, injector: Injector, projectableNodes: any[][] = null,
rootSelectorOrNode: string|any = null): ComponentRef<any> { rootSelectorOrNode: string|any = null): ComponentRef<any> {
const viewDef = resolveViewDefinition(this._viewClass); const viewDef = resolveViewDefinition(this._viewClass);
let componentNodeIndex: number; const componentNodeIndex = viewDef.nodes[0].element.component.index;
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 view = Services.createRootView( const view = Services.createRootView(
injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT); injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT);
const component = asProviderData(view, componentNodeIndex).instance; const component = asProviderData(view, componentNodeIndex).instance;
@ -65,9 +54,14 @@ class ComponentFactory_ implements ComponentFactory<any> {
} }
class ComponentRef_ implements ComponentRef<any> { class ComponentRef_ implements ComponentRef<any> {
constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) {} private _elDef: NodeDef;
get location(): ElementRef { return new ElementRef(asElementData(this._view, 0).renderElement); } constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) {
get injector(): Injector { return new Injector_(this._view, 0); } 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 instance(): any { return this._component; };
get hostView(): ViewRef { return this._viewRef; }; get hostView(): ViewRef { return this._viewRef; };
get changeDetectorRef(): ChangeDetectorRef { 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); } onDestroy(callback: Function): void { this._viewRef.onDestroy(callback); }
} }
export function createViewContainerRef(view: ViewData, elIndex: number): ViewContainerRef { export function createViewContainerRef(view: ViewData, elDef: NodeDef): ViewContainerRef {
return new ViewContainerRef_(view, elIndex); return new ViewContainerRef_(view, elDef);
} }
class ViewContainerRef_ implements ViewContainerRef { class ViewContainerRef_ implements ViewContainerRef {
private _data: ElementData; private _data: ElementData;
constructor(private _view: ViewData, private _elIndex: number) { constructor(private _view: ViewData, private _elDef: NodeDef) {
this._data = asElementData(_view, _elIndex); this._data = asElementData(_view, _elDef.index);
} }
get element(): ElementRef { return new ElementRef(this._data.renderElement); } 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 { get parentInjector(): Injector {
let view = this._view; let view = this._view;
let elIndex = view.def.nodes[this._elIndex].parent; let elDef = this._elDef.parent;
while (elIndex == null && view) { while (!elDef && view) {
elIndex = viewParentElIndex(view); elDef = viewParentEl(view);
view = view.parent; 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 { clear(): void {
@ -200,15 +194,15 @@ class TemplateRef_ implements TemplateRef<any> {
} }
} }
export function createInjector(view: ViewData, elIndex: number): Injector { export function createInjector(view: ViewData, elDef: NodeDef): Injector {
return new Injector_(view, elIndex); return new Injector_(view, elDef);
} }
class Injector_ implements Injector { 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 { get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
return Services.resolveDep( return Services.resolveDep(
this.view, undefined, this.elIndex, this.view, this.elDef, true, {flags: DepFlags.None, token, tokenKey: tokenKey(token)},
{flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue); notFoundValue);
} }
} }

View File

@ -16,7 +16,7 @@ import {resolveDep} from './provider';
import {getQueryValue} from './query'; import {getQueryValue} from './query';
import {createInjector} from './refs'; 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 {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 {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view';
import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';
@ -191,10 +191,10 @@ function debugCheckFn(
if ((binding.type === BindingType.ElementProperty || if ((binding.type === BindingType.ElementProperty ||
binding.type === BindingType.DirectiveProperty) && binding.type === BindingType.DirectiveProperty) &&
checkBinding(view, nodeDef, i, value)) { 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( setBindingDebugInfo(
view.root.renderer, asElementData(view, elIndex).renderElement, binding.nonMinifiedName, view.root.renderer, asElementData(view, elDef.index).renderElement,
value); binding.nonMinifiedName, value);
} }
} }
return (<any>delegate)(view, nodeIndex, argStyle, ...givenValues); return (<any>delegate)(view, nodeIndex, argStyle, ...givenValues);
@ -303,49 +303,37 @@ class DebugContext_ implements DebugContext {
private nodeDef: NodeDef; private nodeDef: NodeDef;
private elView: ViewData; private elView: ViewData;
private elDef: NodeDef; private elDef: NodeDef;
private compProviderIndex: number; private compProviderDef: NodeDef;
constructor(public view: ViewData, public nodeIndex: number) { constructor(public view: ViewData, public nodeIndex: number) {
if (nodeIndex == null) { if (nodeIndex == null) {
this.nodeIndex = 0; this.nodeIndex = 0;
} }
this.nodeDef = view.def.nodes[nodeIndex]; this.nodeDef = view.def.nodes[nodeIndex];
let elIndex = nodeIndex; let elDef = this.nodeDef;
let elView = view; let elView = view;
while (elIndex != null && view.def.nodes[elIndex].type !== NodeType.Element) { while (elDef && elDef.type !== NodeType.Element) {
elIndex = view.def.nodes[elIndex].parent; elDef = elDef.parent;
} }
if (elIndex == null) { if (!elDef) {
while (elIndex == null && elView) { while (!elDef && elView) {
elIndex = viewParentElIndex(elView); elDef = viewParentEl(elView);
elView = elView.parent; elView = elView.parent;
} }
} }
this.elDef = elDef;
this.elView = elView; this.elView = elView;
if (elView) { this.compProviderDef = elView ? this.elDef.element.component : null;
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;
}
} }
get injector(): Injector { return createInjector(this.elView, this.elDef.index); } get injector(): Injector { return createInjector(this.elView, this.elDef); }
get component(): any { get component(): any {
if (this.compProviderIndex != null) { if (this.compProviderDef) {
return asProviderData(this.elView, this.compProviderIndex).instance; return asProviderData(this.elView, this.compProviderDef.index).instance;
} }
return this.view.component; return this.view.component;
} }
get context(): any { get context(): any {
if (this.compProviderIndex != null) { if (this.compProviderDef) {
return asProviderData(this.elView, this.compProviderIndex).instance; return asProviderData(this.elView, this.compProviderDef.index).instance;
} }
return this.view.context; return this.view.context;
} }
@ -385,8 +373,8 @@ class DebugContext_ implements DebugContext {
} }
} }
get componentRenderElement() { get componentRenderElement() {
const view = this.compProviderIndex != null ? const view = this.compProviderDef ?
asProviderData(this.elView, this.compProviderIndex).componentView : asProviderData(this.elView, this.compProviderDef.index).componentView :
this.view; this.view;
const elData = findHostElement(view); const elData = findHostElement(view);
return elData ? elData.renderElement : undefined; return elData ? elData.renderElement : undefined;
@ -402,17 +390,14 @@ function findHostElement(view: ViewData): ElementData {
view = view.parent; view = view.parent;
} }
if (view.parent) { if (view.parent) {
const hostData = asElementData(view.parent, viewParentElIndex(view)); return asElementData(view.parent, viewParentEl(view).index);
return hostData;
} }
return undefined; return undefined;
} }
function collectReferences(view: ViewData, nodeDef: NodeDef, references: {[key: string]: any}) { function collectReferences(view: ViewData, nodeDef: NodeDef, references: {[key: string]: any}) {
for (let queryId in nodeDef.matchedQueries) { for (let refName in nodeDef.references) {
if (queryIdIsReference(queryId)) { references[refName] = getQueryValue(view, nodeDef, nodeDef.references[refName]);
references[queryId.slice(1)] = getQueryValue(view, nodeDef, queryId);
}
} }
} }

View File

@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref';
import {looseIdentical} from '../facade/lang'; import {looseIdentical} from '../facade/lang';
import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types'; 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 { export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
// skip the call to sliceErrorStack itself + the call to this function. // 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, index: undefined,
reverseChildIndex: undefined, reverseChildIndex: undefined,
parent: undefined, parent: undefined,
childFlags: undefined, renderParent: undefined,
childMatchedQueries: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, disposableIndex: undefined,
// regular values // regular values
flags: 0, flags: 0,
matchedQueries: {}, ngContentIndex, childFlags: 0,
childMatchedQueries: 0,
matchedQueries: {},
matchedQueryIds: 0,
references: {}, ngContentIndex,
childCount: 0, bindings, childCount: 0, bindings,
disposableCount: 0, disposableCount: 0,
element: undefined, element: undefined,
@ -50,13 +53,12 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
} }
export function createText(view: ViewData, renderHost: any, def: NodeDef): TextData { export function createText(view: ViewData, renderHost: any, def: NodeDef): TextData {
const parentNode =
def.parent != null ? asElementData(view, def.parent).renderElement : renderHost;
let renderNode: any; let renderNode: any;
const renderer = view.root.renderer; const renderer = view.root.renderer;
renderNode = renderer.createText(def.text.prefix); renderNode = renderer.createText(def.text.prefix);
if (parentNode) { const parentEl = getParentRenderElement(view, renderHost, def);
renderer.appendChild(parentNode, renderNode); if (parentEl) {
renderer.appendChild(parentEl, renderNode);
} }
return {renderText: renderNode}; return {renderText: renderNode};
} }

View File

@ -43,10 +43,11 @@ export interface ViewDefinition {
bindingCount: number; bindingCount: number;
disposableCount: 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. * This includes query ids from templates as well.
* Used as a bloom filter.
*/ */
nodeMatchedQueries: {[queryId: string]: boolean}; nodeMatchedQueries: number;
} }
export type ViewDefinitionFactory = () => ViewDefinition; export type ViewDefinitionFactory = () => ViewDefinition;
@ -94,27 +95,35 @@ export interface NodeDef {
index: number; index: number;
reverseChildIndex: number; reverseChildIndex: number;
flags: NodeFlags; flags: NodeFlags;
parent: number; parent: NodeDef;
renderParent: NodeDef;
/** this is checked against NgContentDef.index to find matched nodes */ /** this is checked against NgContentDef.index to find matched nodes */
ngContentIndex: number; ngContentIndex: number;
/** number of transitive children */ /** number of transitive children */
childCount: number; childCount: number;
/** aggregated NodeFlags for all children **/ /** aggregated NodeFlags for all children (does not include self) **/
childFlags: NodeFlags; childFlags: NodeFlags;
bindingIndex: number; bindingIndex: number;
bindings: BindingDef[]; bindings: BindingDef[];
disposableIndex: number; disposableIndex: number;
disposableCount: 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. * 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. * This includes query ids from templates as well.
* Used as a bloom filter.
*/ */
childMatchedQueries: {[queryId: string]: boolean}; childMatchedQueries: number;
element: ElementDef; element: ElementDef;
provider: ProviderDef; provider: ProviderDef;
text: TextDef; text: TextDef;
@ -150,8 +159,11 @@ export enum NodeFlags {
HasEmbeddedViews = 1 << 8, HasEmbeddedViews = 1 << 8,
HasComponent = 1 << 9, HasComponent = 1 << 9,
HasContentQuery = 1 << 10, HasContentQuery = 1 << 10,
HasViewQuery = 1 << 11, HasStaticQuery = 1 << 11,
LazyProvider = 1 << 12 HasDynamicQuery = 1 << 12,
HasViewQuery = 1 << 13,
LazyProvider = 1 << 14,
PrivateProvider = 1 << 15,
} }
export interface BindingDef { export interface BindingDef {
@ -185,11 +197,17 @@ export interface ElementDef {
attrs: {[name: string]: string}; attrs: {[name: string]: string};
outputs: ElementOutputDef[]; outputs: ElementOutputDef[];
template: ViewDefinition; template: ViewDefinition;
component: NodeDef;
/** /**
* visible providers for DI in the view, * visible public providers for DI in the view,
* as see from this element. * 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; source: string;
} }
@ -229,7 +247,7 @@ export enum DepFlags {
None = 0, None = 0,
SkipSelf = 1 << 0, SkipSelf = 1 << 0,
Optional = 1 << 1, Optional = 1 << 1,
Value = 2 << 2 Value = 2 << 2,
} }
export interface DirectiveOutputDef { export interface DirectiveOutputDef {
@ -251,7 +269,9 @@ export enum PureExpressionType {
} }
export interface QueryDef { export interface QueryDef {
id: string; id: number;
// variant of the id that can be used to check against NodeDef.matchedQueryIds, ...
filterId: number;
bindings: QueryBindingDef[]; bindings: QueryBindingDef[];
} }
@ -287,7 +307,7 @@ export interface ViewData {
def: ViewDefinition; def: ViewDefinition;
root: RootData; root: RootData;
// index of component provider / anchor. // index of component provider / anchor.
parentIndex: number; parentNodeDef: NodeDef;
parent: ViewData; parent: ViewData;
component: any; component: any;
context: any; context: any;
@ -431,7 +451,7 @@ export interface Services {
moveEmbeddedView(elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData; moveEmbeddedView(elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData;
destroyView(view: ViewData): void; destroyView(view: ViewData): void;
resolveDep( resolveDep(
view: ViewData, requestNodeIndex: number, elIndex: number, depDef: DepDef, view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef,
notFoundValue?: any): any; notFoundValue?: any): any;
createDebugContext(view: ViewData, nodeIndex: number): DebugContext; createDebugContext(view: ViewData, nodeIndex: number): DebugContext;
handleEvent: ViewHandleEventFn; handleEvent: ViewHandleEventFn;

View File

@ -17,7 +17,7 @@ import {ViewRef} from '../linker/view_ref';
import {Renderer} from '../render/api'; import {Renderer} from '../render/api';
import {expressionChangedAfterItHasBeenCheckedError, isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors'; 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>(); const _tokenKeyCache = new Map<any, string>();
@ -85,7 +85,7 @@ export function dispatchEvent(
export function declaredViewContainer(view: ViewData): ElementData { export function declaredViewContainer(view: ViewData): ElementData {
if (view.parent) { if (view.parent) {
const parentView = view.parent; const parentView = view.parent;
return asElementData(parentView, view.parentIndex); return asElementData(parentView, view.parentNodeDef.index);
} }
return undefined; return undefined;
} }
@ -95,10 +95,10 @@ export function declaredViewContainer(view: ViewData): ElementData {
* for embedded views, this is the index of the parent node * for embedded views, this is the index of the parent node
* that contains the view container. * that contains the view container.
*/ */
export function viewParentElIndex(view: ViewData): number { export function viewParentEl(view: ViewData): NodeDef {
const parentView = view.parent; const parentView = view.parent;
if (parentView) { if (parentView) {
return parentView.def.nodes[view.parentIndex].parent; return view.parentNodeDef.parent;
} else { } else {
return null; return null;
} }
@ -128,10 +128,6 @@ export function nodeValue(view: ViewData, index: number): any {
return undefined; return undefined;
} }
export function queryIdIsReference(queryId: string): boolean {
return queryId.startsWith('#');
}
export function elementEventFullName(target: string, name: string): string { export function elementEventFullName(target: string, name: string): string {
return target ? `${target}:${name}` : name; return target ? `${target}:${name}` : name;
} }
@ -140,6 +136,45 @@ export function isComponentView(view: ViewData): boolean {
return view.component === view.context && !!view.parent; 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>(); const VIEW_DEFINITION_CACHE = new WeakMap<any, ViewDefinition>();
export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition { export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition {
@ -187,9 +222,14 @@ export function visitRootRenderNodes(
if (action === RenderNodeAction.RemoveChild) { if (action === RenderNodeAction.RemoveChild) {
parentNode = view.root.renderer.parentNode(renderNode(view, view.def.lastRootNode)); 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; export function visitSiblingRenderNodes(
for (let i = 0; i < len; i++) { 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]; const nodeDef = view.def.nodes[i];
if (nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text || if (nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text ||
nodeDef.type === NodeType.NgContent) { nodeDef.type === NodeType.NgContent) {
@ -208,7 +248,7 @@ export function visitProjectedRenderNodes(
compView = compView.parent; compView = compView.parent;
} }
const hostView = compView.parent; const hostView = compView.parent;
const hostElDef = hostView.def.nodes[viewParentElIndex(compView)]; const hostElDef = viewParentEl(compView);
const startIndex = hostElDef.index + 1; const startIndex = hostElDef.index + 1;
const endIndex = hostElDef.index + hostElDef.childCount; const endIndex = hostElDef.index + hostElDef.childCount;
for (let i = startIndex; i <= endIndex; i++) { 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);
}
} }
} }

View File

@ -16,89 +16,121 @@ import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline,
import {checkAndUpdateQuery, createQuery, queryDef} from './query'; import {checkAndUpdateQuery, createQuery, queryDef} from './query';
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text'; import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
import {ArgumentType, ComponentDefinition, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types'; import {ArgumentType, ComponentDefinition, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types';
import {checkBindingNoChanges, isComponentView, queryIdIsReference, resolveViewDefinition, viewParentElIndex} from './util'; import {checkBindingNoChanges, isComponentView, resolveViewDefinition, viewParentEl} from './util';
const NOOP = (): any => undefined; const NOOP = (): any => undefined;
export function viewDef( export function viewDef(
flags: ViewFlags, nodesWithoutIndices: NodeDef[], updateDirectives?: ViewUpdateFn, flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn,
updateRenderer?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, compId?: string, updateRenderer?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, compId?: string,
encapsulation?: ViewEncapsulation, styles?: string[]): ViewDefinition { encapsulation?: ViewEncapsulation, styles?: string[]): ViewDefinition {
// clone nodes and set auto calculated values // clone nodes and set auto calculated values
if (nodesWithoutIndices.length === 0) { if (nodes.length === 0) {
throw new Error(`Illegal State: Views without nodes are not allowed!`); throw new Error(`Illegal State: Views without nodes are not allowed!`);
} }
const nodes: NodeDef[] = new Array(nodesWithoutIndices.length); const reverseChildNodes: NodeDef[] = new Array(nodes.length);
const reverseChildNodes: NodeDef[] = new Array(nodesWithoutIndices.length);
let viewBindingCount = 0; let viewBindingCount = 0;
let viewDisposableCount = 0; let viewDisposableCount = 0;
let viewNodeFlags = 0; let viewNodeFlags = 0;
let viewMatchedQueries: {[queryId: string]: boolean} = {}; let viewMatchedQueries = 0;
let currentParent: NodeDef = null; let currentParent: NodeDef = null;
let currentElementHasPublicProviders = false;
let currentElementHasPrivateProviders = false;
let lastRootNode: NodeDef = null; let lastRootNode: NodeDef = null;
for (let i = 0; i < nodesWithoutIndices.length; i++) { for (let i = 0; i < nodes.length; i++) {
while (currentParent && i > currentParent.index + currentParent.childCount) { while (currentParent && i > currentParent.index + currentParent.childCount) {
const newParent = nodes[currentParent.parent]; const newParent = currentParent.parent;
if (newParent) { if (newParent) {
newParent.childFlags |= currentParent.childFlags; newParent.childFlags |= currentParent.childFlags;
copyQueryMatchesInto(currentParent.childMatchedQueries, newParent.childMatchedQueries); newParent.childMatchedQueries |= currentParent.childMatchedQueries;
} }
currentParent = newParent; currentParent = newParent;
} }
const nodeWithoutIndices = nodesWithoutIndices[i]; const node = nodes[i];
const reverseChildIndex = calculateReverseChildIndex( node.index = i;
currentParent, i, nodeWithoutIndices.childCount, nodesWithoutIndices.length); node.parent = currentParent;
node.bindingIndex = viewBindingCount;
node.disposableIndex = viewDisposableCount;
node.reverseChildIndex =
calculateReverseChildIndex(currentParent, i, node.childCount, nodes.length);
const node = cloneAndModifyNode(nodeWithoutIndices, { let currentRenderParent: NodeDef;
index: i, if (currentParent &&
parent: currentParent ? currentParent.index : undefined, !(currentParent.type === NodeType.Element && currentParent.element.component)) {
bindingIndex: viewBindingCount, // children of components should never be attached!
disposableIndex: viewDisposableCount, reverseChildIndex, if (currentParent && currentParent.type === NodeType.Element && !currentParent.element.name) {
}); currentRenderParent = currentParent.renderParent;
if (node.element) { } else {
node.element = cloneAndModifyElement(node.element, { currentRenderParent = currentParent;
// Use protoypical inheritance to not get O(n^2) complexity...
providerIndices:
Object.create(currentParent ? currentParent.element.providerIndices : null),
});
}
nodes[i] = node;
reverseChildNodes[reverseChildIndex] = node;
validateNode(currentParent, node, nodesWithoutIndices.length);
viewNodeFlags |= node.flags;
copyQueryMatchesInto(node.matchedQueries, viewMatchedQueries);
viewBindingCount += node.bindings.length;
viewDisposableCount += node.disposableCount;
if (currentParent) {
currentParent.childFlags |= node.flags;
copyQueryMatchesInto(node.matchedQueries, currentParent.childMatchedQueries);
if (node.element && node.element.template) {
copyQueryMatchesInto(
node.element.template.nodeMatchedQueries, currentParent.childMatchedQueries);
} }
} }
node.renderParent = currentRenderParent;
if (node.element) {
const elDef = node.element;
elDef.publicProviders =
currentParent ? currentParent.element.publicProviders : Object.create(null);
elDef.allProviders = elDef.publicProviders;
// Note: We assume that all providers of an element are before any child element!
currentElementHasPublicProviders = false;
currentElementHasPrivateProviders = false;
}
reverseChildNodes[node.reverseChildIndex] = node;
validateNode(currentParent, node, nodes.length);
viewNodeFlags |= node.flags;
viewMatchedQueries |= node.matchedQueryIds;
if (node.element && node.element.template) {
viewMatchedQueries |= node.element.template.nodeMatchedQueries;
}
if (currentParent) {
currentParent.childFlags |= node.flags;
currentParent.childMatchedQueries |= node.matchedQueryIds;
if (node.element && node.element.template) {
currentParent.childMatchedQueries |= node.element.template.nodeMatchedQueries;
}
}
viewBindingCount += node.bindings.length;
viewDisposableCount += node.disposableCount;
if (!currentParent) { if (!currentParent) {
lastRootNode = node; lastRootNode = node;
} }
if (node.type === NodeType.Provider || node.type === NodeType.Directive) { if (node.type === NodeType.Provider || node.type === NodeType.Directive) {
currentParent.element.providerIndices[node.provider.tokenKey] = i; if (!currentElementHasPublicProviders) {
} currentElementHasPublicProviders = true;
if (node.query) { // Use protoypical inheritance to not get O(n^2) complexity...
const elementDef = nodes[currentParent.parent]; currentParent.element.publicProviders =
elementDef.element.providerIndices[node.query.id] = i; Object.create(currentParent.element.publicProviders);
currentParent.element.allProviders = currentParent.element.publicProviders;
}
const isPrivateService = (node.flags & NodeFlags.PrivateProvider) !== 0;
const isComponent = (node.flags & NodeFlags.HasComponent) !== 0;
if (!isPrivateService || isComponent) {
currentParent.element.publicProviders[node.provider.tokenKey] = node;
} else {
if (!currentElementHasPrivateProviders) {
currentElementHasPrivateProviders = true;
// Use protoypical inheritance to not get O(n^2) complexity...
currentParent.element.allProviders = Object.create(currentParent.element.publicProviders);
}
currentParent.element.allProviders[node.provider.tokenKey] = node;
}
if (isComponent) {
currentParent.element.component = node;
}
} }
if (node.childCount) { if (node.childCount) {
currentParent = node; currentParent = node;
} }
} }
while (currentParent) { while (currentParent) {
const newParent = nodes[currentParent.parent]; const newParent = currentParent.parent;
if (newParent) { if (newParent) {
newParent.childFlags |= currentParent.childFlags; newParent.childFlags |= currentParent.childFlags;
copyQueryMatchesInto(currentParent.childMatchedQueries, newParent.childMatchedQueries); newParent.childMatchedQueries |= currentParent.childMatchedQueries;
} }
currentParent = newParent; currentParent = newParent;
} }
@ -117,15 +149,6 @@ export function viewDef(
}; };
} }
function copyQueryMatchesInto(
source: {[queryId: string]: any}, target: {[queryId: string]: boolean}) {
for (let prop in source) {
if (!queryIdIsReference(prop)) {
target[prop] = true;
}
}
}
function calculateReverseChildIndex( function calculateReverseChildIndex(
currentParent: NodeDef, i: number, childCount: number, nodeCount: number) { currentParent: NodeDef, i: number, childCount: number, nodeCount: number) {
// Notes about reverse child order: // Notes about reverse child order:
@ -196,57 +219,10 @@ function validateNode(parent: NodeDef, node: NodeDef, nodeCount: number) {
} }
} }
function cloneAndModifyNode(nodeDef: NodeDef, values: {
index: number,
reverseChildIndex: number,
parent: number,
bindingIndex: number,
disposableIndex: number,
}): NodeDef {
// Attention: don't use copyInto here to prevent v8 from treating this object
// as a dictionary!
return {
type: nodeDef.type,
index: values.index,
reverseChildIndex: values.reverseChildIndex,
parent: values.parent,
childFlags: 0,
childMatchedQueries: {},
bindingIndex: values.bindingIndex,
disposableIndex: values.disposableIndex,
flags: nodeDef.flags,
matchedQueries: nodeDef.matchedQueries,
ngContentIndex: nodeDef.ngContentIndex,
childCount: nodeDef.childCount,
bindings: nodeDef.bindings,
disposableCount: nodeDef.disposableCount,
element: nodeDef.element,
provider: nodeDef.provider,
text: nodeDef.text,
pureExpression: nodeDef.pureExpression,
query: nodeDef.query,
ngContent: nodeDef.ngContent
};
}
function cloneAndModifyElement(
elementDef: ElementDef, values: {providerIndices: {[tokenKey: string]: number}}): ElementDef {
// Attention: don't use copyInto here to prevent v8 from treating this object
// as a dictionary!
return {
name: elementDef.name,
attrs: elementDef.attrs,
outputs: elementDef.outputs,
template: elementDef.template,
providerIndices: values.providerIndices,
source: elementDef.source
};
}
export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData { export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
// embedded views are seen as siblings to the anchor, so we need // embedded views are seen as siblings to the anchor, so we need
// to get the parent of the anchor and use it as parentIndex. // to get the parent of the anchor and use it as parentIndex.
const view = createView(parent.root, parent, anchorDef.index, anchorDef.element.template); const view = createView(parent.root, parent, anchorDef, anchorDef.element.template);
initView(view, parent.component, context); initView(view, parent.component, context);
createViewNodes(view); createViewNodes(view);
return view; return view;
@ -260,13 +236,13 @@ export function createRootView(root: RootData, def: ViewDefinition, context?: an
} }
function createView( function createView(
root: RootData, parent: ViewData, parentIndex: number, def: ViewDefinition): ViewData { root: RootData, parent: ViewData, parentNodeDef: NodeDef, def: ViewDefinition): ViewData {
const nodes: NodeData[] = new Array(def.nodes.length); const nodes: NodeData[] = new Array(def.nodes.length);
const disposables = def.disposableCount ? new Array(def.disposableCount) : undefined; const disposables = def.disposableCount ? new Array(def.disposableCount) : undefined;
const view: ViewData = { const view: ViewData = {
def, def,
parent, parent,
parentIndex, parentNodeDef,
context: undefined, context: undefined,
component: undefined, nodes, component: undefined, nodes,
state: ViewState.FirstCheck | ViewState.ChecksEnabled, root, state: ViewState.FirstCheck | ViewState.ChecksEnabled, root,
@ -283,7 +259,7 @@ function initView(view: ViewData, component: any, context: any) {
function createViewNodes(view: ViewData) { function createViewNodes(view: ViewData) {
let renderHost: any; let renderHost: any;
if (isComponentView(view)) { if (isComponentView(view)) {
renderHost = asElementData(view.parent, viewParentElIndex(view)).renderElement; renderHost = asElementData(view.parent, viewParentEl(view).index).renderElement;
} }
const def = view.def; const def = view.def;
@ -316,7 +292,7 @@ function createViewNodes(view: ViewData) {
// the component view. Therefore, we create the component view first // the component view. Therefore, we create the component view first
// and set the ProviderData in ViewData, and then instantiate the provider. // and set the ProviderData in ViewData, and then instantiate the provider.
const componentView = createView( const componentView = createView(
view.root, view, nodeDef.index, resolveViewDefinition(nodeDef.provider.component)); view.root, view, nodeDef, resolveViewDefinition(nodeDef.provider.component));
const providerData = <ProviderData>{componentView, instance: undefined}; const providerData = <ProviderData>{componentView, instance: undefined};
nodes[i] = providerData as any; nodes[i] = providerData as any;
const instance = providerData.instance = createDirectiveInstance(view, nodeDef); const instance = providerData.instance = createDirectiveInstance(view, nodeDef);
@ -344,21 +320,29 @@ function createViewNodes(view: ViewData) {
// Create the ViewData.nodes of component views after we created everything else, // Create the ViewData.nodes of component views after we created everything else,
// so that e.g. ng-content works // so that e.g. ng-content works
execComponentViewsAction(view, ViewAction.CreateViewNodes); execComponentViewsAction(view, ViewAction.CreateViewNodes);
// fill static content and view queries
execQueriesAction(
view, NodeFlags.HasContentQuery | NodeFlags.HasViewQuery, NodeFlags.HasStaticQuery,
QueryAction.CheckAndUpdate);
} }
export function checkNoChangesView(view: ViewData) { export function checkNoChangesView(view: ViewData) {
Services.updateDirectives(checkNoChangesNode, view); Services.updateDirectives(checkNoChangesNode, view);
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges); execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckNoChanges); execQueriesAction(
view, NodeFlags.HasContentQuery, NodeFlags.HasDynamicQuery, QueryAction.CheckNoChanges);
Services.updateRenderer(checkNoChangesNode, view); Services.updateRenderer(checkNoChangesNode, view);
execComponentViewsAction(view, ViewAction.CheckNoChanges); execComponentViewsAction(view, ViewAction.CheckNoChanges);
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckNoChanges); execQueriesAction(
view, NodeFlags.HasViewQuery, NodeFlags.HasDynamicQuery, QueryAction.CheckNoChanges);
} }
export function checkAndUpdateView(view: ViewData) { export function checkAndUpdateView(view: ViewData) {
Services.updateDirectives(checkAndUpdateNode, view); Services.updateDirectives(checkAndUpdateNode, view);
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate); execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckAndUpdate); execQueriesAction(
view, NodeFlags.HasContentQuery, NodeFlags.HasDynamicQuery, QueryAction.CheckAndUpdate);
callLifecycleHooksChildrenFirst( callLifecycleHooksChildrenFirst(
view, NodeFlags.AfterContentChecked | view, NodeFlags.AfterContentChecked |
@ -367,7 +351,8 @@ export function checkAndUpdateView(view: ViewData) {
Services.updateRenderer(checkAndUpdateNode, view); Services.updateRenderer(checkAndUpdateNode, view);
execComponentViewsAction(view, ViewAction.CheckAndUpdate); execComponentViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckAndUpdate); execQueriesAction(
view, NodeFlags.HasViewQuery, NodeFlags.HasDynamicQuery, QueryAction.CheckAndUpdate);
callLifecycleHooksChildrenFirst( callLifecycleHooksChildrenFirst(
view, NodeFlags.AfterViewChecked | view, NodeFlags.AfterViewChecked |
@ -571,14 +556,17 @@ enum QueryAction {
CheckNoChanges CheckNoChanges
} }
function execQueriesAction(view: ViewData, queryFlags: NodeFlags, action: QueryAction) { function execQueriesAction(
if (!(view.def.nodeFlags & queryFlags)) { view: ViewData, queryFlags: NodeFlags, staticDynamicQueryFlag: NodeFlags, action: QueryAction) {
if (!(view.def.nodeFlags & queryFlags) || !(view.def.nodeFlags & staticDynamicQueryFlag)) {
return; return;
} }
const nodeCount = view.def.nodes.length; const nodeCount = view.def.nodes.length;
for (let i = 0; i < nodeCount; i++) { for (let i = 0; i < nodeCount; i++) {
const nodeDef = view.def.nodes[i]; const nodeDef = view.def.nodes[i];
if (nodeDef.flags & queryFlags) { if ((nodeDef.flags & queryFlags) && (nodeDef.flags & staticDynamicQueryFlag)) {
const elDef = nodeDef.parent.parent;
Services.setCurrentNode(view, nodeDef.index); Services.setCurrentNode(view, nodeDef.index);
switch (action) { switch (action) {
case QueryAction.CheckAndUpdate: case QueryAction.CheckAndUpdate:
@ -588,8 +576,9 @@ function execQueriesAction(view: ViewData, queryFlags: NodeFlags, action: QueryA
checkNoChangesQuery(view, nodeDef); checkNoChangesQuery(view, nodeDef);
break; break;
} }
} else if ((nodeDef.childFlags & queryFlags) === 0) { }
// no child has a content query if (!(nodeDef.childFlags & queryFlags) || !(nodeDef.childFlags & staticDynamicQueryFlag)) {
// no child has a matching query
// then skip the children // then skip the children
i += nodeDef.childCount; i += nodeDef.childCount;
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {dirtyParentQuery} from './query'; import {dirtyParentQueries} from './query';
import {ElementData, NodeData, NodeDef, NodeFlags, NodeType, ViewData, asElementData, asProviderData, asTextData} from './types'; import {ElementData, NodeData, NodeDef, NodeFlags, NodeType, ViewData, asElementData, asProviderData, asTextData} from './types';
import {RenderNodeAction, declaredViewContainer, isComponentView, renderNode, rootRenderNodes, visitProjectedRenderNodes, visitRootRenderNodes} from './util'; import {RenderNodeAction, declaredViewContainer, isComponentView, renderNode, rootRenderNodes, visitProjectedRenderNodes, visitRootRenderNodes} from './util';
@ -25,9 +25,7 @@ export function attachEmbeddedView(elementData: ElementData, viewIndex: number,
projectedViews.push(view); projectedViews.push(view);
} }
for (let queryId in view.def.nodeMatchedQueries) { dirtyParentQueries(view);
dirtyParentQuery(queryId, view);
}
const prevView = viewIndex > 0 ? embeddedViews[viewIndex - 1] : null; const prevView = viewIndex > 0 ? embeddedViews[viewIndex - 1] : null;
renderAttachEmbeddedView(elementData, prevView, view); renderAttachEmbeddedView(elementData, prevView, view);
@ -47,9 +45,7 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex: number):
removeFromArray(projectedViews, projectedViews.indexOf(view)); removeFromArray(projectedViews, projectedViews.indexOf(view));
} }
for (let queryId in view.def.nodeMatchedQueries) { dirtyParentQueries(view);
dirtyParentQuery(queryId, view);
}
renderDetachEmbeddedView(elementData, view); renderDetachEmbeddedView(elementData, view);
@ -69,9 +65,7 @@ export function moveEmbeddedView(
// Note: Don't need to change projectedViews as the order in there // Note: Don't need to change projectedViews as the order in there
// as always invalid... // as always invalid...
for (let queryId in view.def.nodeMatchedQueries) { dirtyParentQueries(view);
dirtyParentQuery(queryId, view);
}
renderDetachEmbeddedView(elementData, view); renderDetachEmbeddedView(elementData, view);
const prevView = newViewIndex > 0 ? embeddedViews[newViewIndex - 1] : null; const prevView = newViewIndex > 0 ? embeddedViews[newViewIndex - 1] : null;

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver} from '@angular/core'; import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver} from '@angular/core';
import {noComponentFactoryError} from '@angular/core/src/linker/component_factory_resolver'; import {noComponentFactoryError} from '@angular/core/src/linker/component_factory_resolver';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
@ -14,6 +15,19 @@ import {Console} from '../../src/console';
export function main() { 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('jit', () => { declareTests({useJit: true}); });
describe('no jit', () => { declareTests({useJit: false}); }); describe('no jit', () => { declareTests({useJit: false}); });
} }

View File

@ -1250,7 +1250,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
.toThrowError(`Directive ${stringify(SomeDirective)} has no selector, please add it!`); .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>; let noSelectorComponentFactory: ComponentFactory<SomeComponent>;
@Component({template: '----'}) @Component({template: '----'})

View File

@ -6,14 +6,27 @@
* found in the LICENSE file at https://angular.io/license * 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 {Component, Directive, ElementRef, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {TestBed} from '@angular/core/testing'; 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 {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { 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', () => { describe('projection', () => {
beforeEach(() => TestBed.configureTestingModule({declarations: [MainComp, OtherComp, Simple]})); beforeEach(() => TestBed.configureTestingModule({declarations: [MainComp, OtherComp, Simple]}));
@ -365,7 +378,7 @@ export function main() {
expect(main.nativeElement).toHaveText('TREE(0:TREE2(1:TREE(2:)))'); 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', () => { it('should support native content projection and isolate styles per component', () => {
TestBed.configureTestingModule({declarations: [SimpleNative1, SimpleNative2]}); TestBed.configureTestingModule({declarations: [SimpleNative1, SimpleNative2]});
TestBed.overrideComponent(MainComp, { TestBed.overrideComponent(MainComp, {
@ -383,7 +396,7 @@ export function main() {
}); });
} }
if (getDOM().supportsDOMEvents()) { if (!viewEngine && getDOM().supportsDOMEvents()) {
it('should support non emulated styles', () => { it('should support non emulated styles', () => {
TestBed.configureTestingModule({declarations: [OtherComp]}); TestBed.configureTestingModule({declarations: [OtherComp]});
TestBed.overrideComponent(MainComp, { TestBed.overrideComponent(MainComp, {

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {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 {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 {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers'; 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'; import {stringify} from '../../src/facade/lang';
export function main() { 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', () => { describe('Query API', () => {
beforeEach(() => TestBed.configureTestingModule({ beforeEach(() => TestBed.configureTestingModule({
@ -267,9 +281,10 @@ export function main() {
it('should contain the first descendant content child templateRef', () => { it('should contain the first descendant content child templateRef', () => {
const template = '<needs-content-child-template-ref-app>' + const template = '<needs-content-child-template-ref-app>' +
'</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'); expect(view.nativeElement).toHaveText('OUTER');
}); });

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license * 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 {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 {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 {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
@ -190,6 +190,19 @@ class TestComp {
} }
export function main() { 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>( function createComponentFixture<T>(
template: string, providers: Provider[] = null, comp: Type<T> = null): ComponentFixture<T> { template: string, providers: Provider[] = null, comp: Type<T> = null): ComponentFixture<T> {
if (!comp) { if (!comp) {
@ -213,9 +226,10 @@ export function main() {
// On CJS fakeAsync is not supported... // On CJS fakeAsync is not supported...
if (!getDOM().supportsDOMEvents()) return; if (!getDOM().supportsDOMEvents()) return;
beforeEach(() => TestBed.configureTestingModule({declarations: [TestComp]})); beforeEach(() => TestBed.configureTestingModule({
declarations: [TestComp],
beforeEachProviders(() => [{provide: 'appService', useValue: 'appService'}]); providers: [{provide: 'appService', useValue: 'appService'}]
}));
describe('injection', () => { describe('injection', () => {
it('should instantiate directives that have no dependencies', () => { it('should instantiate directives that have no dependencies', () => {
@ -591,20 +605,19 @@ export function main() {
.toBe(el.children[0].nativeElement); .toBe(el.children[0].nativeElement);
}); });
it('should inject ChangeDetectorRef of the component\'s view into the component via a proxy', it('should inject ChangeDetectorRef of the component\'s view into the component', () => {
() => { TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]});
TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]}); const cf = createComponentFixture('<div componentNeedsChangeDetectorRef></div>');
const cf = createComponentFixture('<div componentNeedsChangeDetectorRef></div>'); cf.detectChanges();
cf.detectChanges(); const compEl = cf.debugElement.children[0];
const compEl = cf.debugElement.children[0]; const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); comp.counter = 1;
comp.counter = 1; cf.detectChanges();
cf.detectChanges(); expect(compEl.nativeElement).toHaveText('0');
expect(compEl.nativeElement).toHaveText('0'); comp.changeDetectorRef.markForCheck();
comp.changeDetectorRef.markForCheck(); cf.detectChanges();
cf.detectChanges(); expect(compEl.nativeElement).toHaveText('1');
expect(compEl.nativeElement).toHaveText('1'); });
});
it('should inject ChangeDetectorRef of the containing component into directives', () => { it('should inject ChangeDetectorRef of the containing component into directives', () => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
@ -624,9 +637,9 @@ export function main() {
cf.detectChanges(); cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0'); expect(compEl.nativeElement).toHaveText('0');
expect(compEl.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef) expect(compEl.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef)
.toBe(comp.changeDetectorRef); .toEqual(comp.changeDetectorRef);
expect(compEl.children[1].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef) expect(compEl.children[1].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef)
.toBe(comp.changeDetectorRef); .toEqual(comp.changeDetectorRef);
comp.changeDetectorRef.markForCheck(); comp.changeDetectorRef.markForCheck();
cf.detectChanges(); cf.detectChanges();
expect(compEl.nativeElement).toHaveText('1'); expect(compEl.nativeElement).toHaveText('1');
@ -687,7 +700,7 @@ export function main() {
'<div [simpleDirective]="true | pipeNeedsChangeDetectorRef" directiveNeedsChangeDetectorRef></div>'); '<div [simpleDirective]="true | pipeNeedsChangeDetectorRef" directiveNeedsChangeDetectorRef></div>');
const cdRef = const cdRef =
el.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef; 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', () => { it('should cache pure pipes', () => {

View File

@ -33,6 +33,8 @@ export function main() {
return {rootNodes, view}; return {rootNodes, view};
} }
const someQueryId = 1;
class AService {} class AService {}
class QueryService { class QueryService {
@ -42,19 +44,24 @@ export function main() {
function contentQueryProviders() { function contentQueryProviders() {
return [ return [
directiveDef(NodeFlags.None, null, 1, QueryService, []), 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) { function viewQueryProviders(compView: ViewDefinition) {
return [ return [
directiveDef(NodeFlags.None, null, 1, QueryService, [], null, null, () => compView), 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() { function aServiceProvider() {
return directiveDef(NodeFlags.None, [['query1', QueryValueType.Provider]], 0, AService, []); return directiveDef(
NodeFlags.None, [[someQueryId, QueryValueType.Provider]], 0, AService, []);
} }
describe('content queries', () => { describe('content queries', () => {
@ -251,7 +258,9 @@ export function main() {
const {view} = createAndGetRootNodes(compViewDef([ const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 4, 'div'), elementDef(NodeFlags.None, null, null, 4, 'div'),
directiveDef(NodeFlags.None, null, 1, QueryService, []), directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All}), queryDef(
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.All}),
aServiceProvider(), aServiceProvider(),
aServiceProvider(), aServiceProvider(),
])); ]));
@ -274,7 +283,9 @@ export function main() {
const {view} = createAndGetRootNodes(compViewDef([ const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 4, 'div'), elementDef(NodeFlags.None, null, null, 4, 'div'),
directiveDef(NodeFlags.None, null, 1, QueryService, []), directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}), queryDef(
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.First}),
aServiceProvider(), aServiceProvider(),
aServiceProvider(), aServiceProvider(),
])); ]));
@ -293,9 +304,11 @@ export function main() {
} }
const {view} = createAndGetRootNodes(compViewDef([ 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, []), 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); Services.checkAndUpdateView(view);
@ -311,10 +324,12 @@ export function main() {
const {view} = createAndGetRootNodes(compViewDef([ const {view} = createAndGetRootNodes(compViewDef([
anchorDef( anchorDef(
NodeFlags.None, [['query1', QueryValueType.TemplateRef]], null, 2, NodeFlags.None, [[someQueryId, QueryValueType.TemplateRef]], null, 2,
embeddedViewDef([anchorDef(NodeFlags.None, null, null, 0)])), embeddedViewDef([anchorDef(NodeFlags.None, null, null, 0)])),
directiveDef(NodeFlags.None, null, 1, QueryService, []), 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); Services.checkAndUpdateView(view);
@ -329,9 +344,11 @@ export function main() {
} }
const {view} = createAndGetRootNodes(compViewDef([ 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, []), 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); Services.checkAndUpdateView(view);
@ -367,7 +384,7 @@ export function main() {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(err.message) expect(err.message)
.toBe( .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); const debugCtx = getDebugContext(err);
expect(debugCtx.view).toBe(view); expect(debugCtx.view).toBe(view);
expect(debugCtx.nodeIndex).toBe(2); expect(debugCtx.nodeIndex).toBe(2);
@ -381,7 +398,9 @@ export function main() {
const {view} = createAndGetRootNodes(compViewDef([ const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 3, 'div'), elementDef(NodeFlags.None, null, null, 3, 'div'),
directiveDef(NodeFlags.None, null, 1, QueryService, []), directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All}), queryDef(
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.All}),
aServiceProvider(), aServiceProvider(),
])); ]));

View File

@ -39,7 +39,7 @@ export function main() {
directiveDef( directiveDef(
NodeFlags.None, null, 0, AComp, [], null, null, NodeFlags.None, null, 0, AComp, [], null, null,
() => compViewDef([ () => 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']) directiveDef(NodeFlags.None, null, 0, AService, []), textDef(null, ['a'])
])), ])),
])); ]));

View File

@ -7,6 +7,7 @@
*/ */
import {NodeFlags, QueryValueType, ViewData, ViewDefinition, ViewFlags, anchorDef, directiveDef, elementDef, textDef, viewDef} from '@angular/core/src/view/index'; 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() { export function main() {
describe('viewDef', () => { describe('viewDef', () => {
@ -76,7 +77,7 @@ export function main() {
describe('parent', () => { describe('parent', () => {
function parents(viewDef: ViewDefinition): number[] { 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', () => { it('should calculate parents for one level', () => {
@ -86,7 +87,7 @@ export function main() {
textDef(null, ['a']), 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', () => { it('should calculate parents for one level, multiple roots', () => {
@ -98,7 +99,7 @@ export function main() {
textDef(null, ['a']), 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', () => { it('should calculate parents for multiple levels', () => {
@ -111,7 +112,7 @@ export function main() {
textDef(null, ['a']), 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', () => { describe('childMatchedQueries', () => {
function childMatchedQueries(viewDef: ViewDefinition): string[][] { function childMatchedQueries(viewDef: ViewDefinition): number[] {
return viewDef.nodes.map(node => Object.keys(node.childMatchedQueries).sort()); return viewDef.nodes.map(node => node.childMatchedQueries);
} }
it('should calculate childMatchedQueries for one level', () => { it('should calculate childMatchedQueries for one level', () => {
const vd = viewDef(ViewFlags.None, [ const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 1, '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'], []]); expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), 0]);
}); });
it('should calculate childMatchedQueries for two levels', () => { it('should calculate childMatchedQueries for two levels', () => {
const vd = viewDef(ViewFlags.None, [ const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 2, 'span'), elementDef(NodeFlags.None, null, null, 2, 'span'),
elementDef(NodeFlags.None, null, null, 1, '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', () => { it('should calculate childMatchedQueries for one level, multiple roots', () => {
const vd = viewDef(ViewFlags.None, [ const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 1, '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'), elementDef(NodeFlags.None, null, null, 2, 'span'),
directiveDef(NodeFlags.None, [['q2', QueryValueType.Provider]], 0, AService, []), directiveDef(NodeFlags.None, [[2, QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [['q3', 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', () => { it('should calculate childMatchedQueries for multiple levels', () => {
const vd = viewDef(ViewFlags.None, [ const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 2, 'span'), elementDef(NodeFlags.None, null, null, 2, 'span'),
elementDef(NodeFlags.None, null, null, 1, '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'), elementDef(NodeFlags.None, null, null, 2, 'span'),
directiveDef(NodeFlags.None, [['q2', QueryValueType.Provider]], 0, AService, []), directiveDef(NodeFlags.None, [[2, QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [['q3', 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', () => { it('should included embedded views into childMatchedQueries', () => {
@ -231,12 +236,12 @@ export function main() {
() => viewDef( () => viewDef(
ViewFlags.None, 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, // 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]);
}); });
}); });
}); });

View File

@ -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 { selectRootElement(selectorOrNode: string|any, debugInfo?: any): any {
let el: any = typeof selectorOrNode === 'string' ? document.querySelector(selectorOrNode) : let el: any = typeof selectorOrNode === 'string' ? document.querySelector(selectorOrNode) :

View File

@ -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 { selectRootElement(selectorOrNode: string|any, debugInfo?: any): any {
let el: any; let el: any;