diff --git a/modules/@angular/compiler/src/compile_metadata.ts b/modules/@angular/compiler/src/compile_metadata.ts index 7045037543..8a034ad775 100644 --- a/modules/@angular/compiler/src/compile_metadata.ts +++ b/modules/@angular/compiler/src/compile_metadata.ts @@ -304,6 +304,7 @@ export interface CompileDirectiveSummary extends CompileTypeSummary { providers: CompileProviderMetadata[]; viewProviders: CompileProviderMetadata[]; queries: CompileQueryMetadata[]; + viewQueries: CompileQueryMetadata[]; entryComponents: CompileEntryComponentMetadata[]; changeDetection: ChangeDetectionStrategy; template: CompileTemplateSummary; @@ -480,6 +481,7 @@ export class CompileDirectiveMetadata { providers: this.providers, viewProviders: this.viewProviders, queries: this.queries, + viewQueries: this.viewQueries, entryComponents: this.entryComponents, changeDetection: this.changeDetection, template: this.template && this.template.toSummary(), diff --git a/modules/@angular/compiler/src/identifiers.ts b/modules/@angular/compiler/src/identifiers.ts index 935b7ee789..9fa5987bf1 100644 --- a/modules/@angular/compiler/src/identifiers.ts +++ b/modules/@angular/compiler/src/identifiers.ts @@ -368,6 +368,12 @@ export class Identifiers { {name: 'ɵviewEngine', moduleUrl: CORE, member: 'pipeDef', runtime: ɵviewEngine.pipeDef}; static nodeValue: IdentifierSpec = {name: 'ɵviewEngine', moduleUrl: CORE, member: 'nodeValue', runtime: ɵviewEngine.nodeValue}; + static ngContentDef: IdentifierSpec = { + name: 'ɵviewEngine', + moduleUrl: CORE, + member: 'ngContentDef', + runtime: ɵviewEngine.ngContentDef + }; static unwrapValue: IdentifierSpec = { name: 'ɵviewEngine', moduleUrl: CORE, diff --git a/modules/@angular/compiler/src/provider_analyzer.ts b/modules/@angular/compiler/src/provider_analyzer.ts index 20cc773ad9..b58f2c9112 100644 --- a/modules/@angular/compiler/src/provider_analyzer.ts +++ b/modules/@angular/compiler/src/provider_analyzer.ts @@ -11,7 +11,7 @@ import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveS import {isBlank, isPresent} from './facade/lang'; import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers'; import {ParseError, ParseSourceSpan} from './parse_util'; -import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, QueryId, QueryMatch, ReferenceAst} from './template_parser/template_ast'; +import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst} from './template_parser/template_ast'; export class ProviderError extends ParseError { constructor(message: string, span: ParseSourceSpan) { super(span, message); } @@ -19,7 +19,7 @@ export class ProviderError extends ParseError { export interface QueryWithId { meta: CompileQueryMetadata; - id: QueryId; + queryId: number; } export class ProviderViewContext { @@ -57,16 +57,21 @@ export class ProviderElementContext { constructor( public viewContext: ProviderViewContext, private _parent: ProviderElementContext, private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[], - refs: ReferenceAst[], private _sourceSpan: ParseSourceSpan) { + refs: ReferenceAst[], isTemplate: boolean, contentQueryStartId: number, + private _sourceSpan: ParseSourceSpan) { this._attrs = {}; attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value); const directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive); this._allProviders = _resolveProvidersFromDirectives(directivesMeta, _sourceSpan, viewContext.errors); - this._contentQueries = _getContentQueries(this.depth, directivesMeta); + this._contentQueries = _getContentQueries(contentQueryStartId, directivesMeta); Array.from(this._allProviders.values()).forEach((provider) => { this._addQueryReadsTo(provider.token, provider.token, this._queriedTokens); }); + if (isTemplate) { + const templateRefId = createIdentifierToken(Identifiers.TemplateRef); + this._addQueryReadsTo(templateRefId, templateRefId, this._queriedTokens); + } refs.forEach((refAst) => { let defaultQueryValue = refAst.value || createIdentifierToken(Identifiers.ElementRef); this._addQueryReadsTo({value: refAst.name}, defaultQueryValue, this._queriedTokens); @@ -91,16 +96,6 @@ export class ProviderElementContext { }); } - get depth(): number { - let d = 0; - let current: ProviderElementContext = this; - while (current._parent) { - d++; - current = current._parent; - } - return d; - } - get transformProviders(): ProviderAst[] { return Array.from(this._transformedProviders.values()); } @@ -133,7 +128,7 @@ export class ProviderElementContext { queryMatches = []; queryReadTokens.set(tokenRef, queryMatches); } - queryMatches.push({query: query.id, value: queryValue}); + queryMatches.push({queryId: query.queryId, value: queryValue}); }); } @@ -483,24 +478,24 @@ function _resolveProviders( function _getViewQueries(component: CompileDirectiveMetadata): Map { + // Note: queries start with id 1 so we can use the number in a Bloom filter! + let viewQueryId = 1; const viewQueries = new Map(); if (component.viewQueries) { component.viewQueries.forEach( - (query, queryIndex) => _addQueryToTokenMap( - viewQueries, - {meta: query, id: {elementDepth: null, directiveIndex: null, queryIndex: queryIndex}})); + (query) => _addQueryToTokenMap(viewQueries, {meta: query, queryId: viewQueryId++})); } return viewQueries; } function _getContentQueries( - elementDepth: number, directives: CompileDirectiveSummary[]): Map { + contentQueryStartId: number, directives: CompileDirectiveSummary[]): Map { + let contentQueryId = contentQueryStartId; const contentQueries = new Map(); directives.forEach((directive, directiveIndex) => { if (directive.queries) { directive.queries.forEach( - (query, queryIndex) => _addQueryToTokenMap( - contentQueries, {meta: query, id: {elementDepth, directiveIndex, queryIndex}})); + (query) => _addQueryToTokenMap(contentQueries, {meta: query, queryId: contentQueryId++})); } }); return contentQueries; diff --git a/modules/@angular/compiler/src/template_parser/template_ast.ts b/modules/@angular/compiler/src/template_parser/template_ast.ts index e3acdf4e55..ead3cff538 100644 --- a/modules/@angular/compiler/src/template_parser/template_ast.ts +++ b/modules/@angular/compiler/src/template_parser/template_ast.ts @@ -170,7 +170,7 @@ export class DirectiveAst implements TemplateAst { constructor( public directive: CompileDirectiveSummary, public inputs: BoundDirectivePropertyAst[], public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[], - public sourceSpan: ParseSourceSpan) {} + public contentQueryStartId: number, public sourceSpan: ParseSourceSpan) {} visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitDirective(this, context); } @@ -241,17 +241,8 @@ export enum PropertyBindingType { Animation } -/** - * This id differentiates a query on an element from any query on any child. - */ -export interface QueryId { - elementDepth: number; - directiveIndex: number; - queryIndex: number; -} - export interface QueryMatch { - query: QueryId; + queryId: number; value: CompileTokenMetadata; } diff --git a/modules/@angular/compiler/src/template_parser/template_parser.ts b/modules/@angular/compiler/src/template_parser/template_parser.ts index ea9e94ea03..002d4a8d13 100644 --- a/modules/@angular/compiler/src/template_parser/template_parser.ts +++ b/modules/@angular/compiler/src/template_parser/template_parser.ts @@ -207,12 +207,15 @@ export class TemplateParser { class TemplateParseVisitor implements html.Visitor { selectorMatcher = new SelectorMatcher(); directivesIndex = new Map(); - ngContentCount: number = 0; + ngContentCount = 0; + contentQueryStartId: number; constructor( public providerViewContext: ProviderViewContext, directives: CompileDirectiveSummary[], private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry, private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) { + // Note: queries start with id 1 so we can use the number in a Bloom filter! + this.contentQueryStartId = providerViewContext.component.viewQueries.length + 1; directives.forEach((directive, index) => { const selector = CssSelector.parse(directive.selector); this.selectorMatcher.addSelectables(selector, directive); @@ -241,6 +244,7 @@ class TemplateParseVisitor implements html.Visitor { visitComment(comment: html.Comment, context: any): any { return null; } visitElement(element: html.Element, parent: ElementContext): any { + const queryStartIndex = this.contentQueryStartId; const nodeName = element.name; const preparsedElement = preparseElement(element); if (preparsedElement.type === PreparsedElementType.SCRIPT || @@ -322,9 +326,9 @@ class TemplateParseVisitor implements html.Visitor { const providerContext = new ProviderElementContext( this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs, - references, element.sourceSpan); + references, isTemplateElement, queryStartIndex, element.sourceSpan); - const children = html.visitAll( + const children: TemplateAst[] = html.visitAll( preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children, ElementContext.create( isTemplateElement, directiveAsts, @@ -378,6 +382,7 @@ class TemplateParseVisitor implements html.Visitor { } if (hasInlineTemplates) { + const templateQueryStartIndex = this.contentQueryStartId; const templateCssSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs); const {directives: templateDirectiveMetas} = @@ -392,7 +397,7 @@ class TemplateParseVisitor implements html.Visitor { templateDirectiveAsts, templateElementProps, element.sourceSpan); const templateProviderContext = new ProviderElementContext( this.providerViewContext, parent.providerContext, parent.isTemplateElement, - templateDirectiveAsts, [], [], element.sourceSpan); + templateDirectiveAsts, [], [], true, templateQueryStartIndex, element.sourceSpan); templateProviderContext.afterElement(); parsedElement = new EmbeddedTemplateAst( @@ -585,8 +590,11 @@ class TemplateParseVisitor implements html.Visitor { matchedReferences.add(elOrDirRef.name); } }); + const contentQueryStartId = this.contentQueryStartId; + this.contentQueryStartId += directive.queries.length; return new DirectiveAst( - directive, directiveProperties, hostProperties, hostEvents, sourceSpan); + directive, directiveProperties, hostProperties, hostEvents, contentQueryStartId, + sourceSpan); }); elementOrDirectiveRefs.forEach((elOrDirRef) => { @@ -779,7 +787,7 @@ class NonBindableVisitor implements html.Visitor { const attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]); const selector = createElementCssSelector(ast.name, attrNameAndValues); const ngContentIndex = parent.findNgContentIndex(selector); - const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT); + const children: TemplateAst[] = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT); return new ElementAst( ast.name, html.visitAll(this, ast.attrs), [], [], [], [], [], false, [], children, ngContentIndex, ast.sourceSpan, ast.endSourceSpan); diff --git a/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts b/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts index f4751ffab0..80d096060b 100644 --- a/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts +++ b/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts @@ -9,23 +9,24 @@ import {ChangeDetectionStrategy} from '@angular/core'; 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 {CompilerConfig} from '../config'; 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 * as o from '../output/output_ast'; import {convertValueToOutputAst} from '../output/value_util'; import {LifecycleHooks, viewEngine} from '../private_import_core'; 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 {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler'; const CLASS_ATTR = 'class'; const STYLE_ATTR = 'style'; const IMPLICIT_TEMPLATE_VAR = '\$implicit'; +const NG_CONTAINER_TAG = 'ng-container'; @CompilerInjectable() export class ViewCompilerNext extends ViewCompiler { @@ -41,15 +42,16 @@ export class ViewCompilerNext extends ViewCompiler { const compName = identifierName(component.type) + (component.isHost ? `_Host` : ''); let embeddedViewCount = 0; + const staticQueryIds = findStaticQueryIds(template); const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => { const embeddedViewIndex = embeddedViewCount++; const viewName = `view_${compName}_${embeddedViewIndex}`; - return new ViewBuilder(parent, viewName, usedPipes, viewBuilderFactory); + return new ViewBuilder(parent, viewName, usedPipes, staticQueryIds, viewBuilderFactory); }; const visitor = viewBuilderFactory(null); - visitor.visitAll([], template, 0); + visitor.visitAll([], template); const statements: o.Statement[] = []; statements.push(...visitor.build(component)); @@ -93,9 +95,10 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter constructor( private parent: ViewBuilder, public viewName: string, private usedPipes: CompilePipeSummary[], + private staticQueryIds: Map, private viewBuilderFactory: ViewBuilderFactory) {} - visitAll(variables: VariableAst[], astNodes: TemplateAst[], elementDepth: number) { + visitAll(variables: VariableAst[], astNodes: TemplateAst[]) { this.variables = variables; // create the pipes for the pure pipes immediately, so that we know their indices. if (!this.parent) { @@ -106,7 +109,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter }); } - templateVisitAll(this, astNodes, {elementDepth}); + templateVisitAll(this, astNodes); if (astNodes.length === 0 || (this.parent && needsAdditionalRootNode(astNodes[astNodes.length - 1]))) { // 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; } - 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 { // textDef(ngContentIndex: number, constants: string[]): NodeDef; 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; 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; // reserve the space in the nodeDefs array this.nodeDefs.push(null); - const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast, context); + const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast); const childVisitor = this.viewBuilderFactory(this); 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; @@ -241,20 +249,20 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, // childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef; 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) ]); } - visitElement(ast: ElementAst, context: {elementDepth: number}): any { + visitElement(ast: ElementAst, context: any): any { const nodeIndex = this.nodeDefs.length; // reserve the space in the nodeDefs array so we can add children this.nodeDefs.push(null); - const {flags, usedEvents, queryMatchesExpr, hostBindings} = - this._visitElementOrTemplate(nodeIndex, ast, context); + let {flags, usedEvents, queryMatchesExpr, hostBindings} = + this._visitElementOrTemplate(nodeIndex, ast); - templateVisitAll(this, ast.children, {elementDepth: context.elementDepth + 1}); + templateVisitAll(this, ast.children); ast.inputs.forEach( (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; + let elName = ast.name; + if (ast.name === NG_CONTAINER_TAG) { + // Using a null element name creates an anchor. + elName = null; + } + // elementDef( // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, // childCount: number, name: string, fixedAttrs: {[name: string]: string} = {}, @@ -279,22 +293,21 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter // SecurityContext])[], // outputs?: (string | [string, string])[]): NodeDef; this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([ - o.literal(flags), queryMatchesExpr, o.NULL_EXPR, o.literal(childCount), o.literal(ast.name), - fixedAttrsDef(ast), inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR, + o.literal(flags), queryMatchesExpr, o.literal(ast.ngContentIndex), o.literal(childCount), + o.literal(elName), fixedAttrsDef(ast), + inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR, outputDefs.length ? o.literalArr(outputDefs) : o.NULL_EXPR ]); } - private _visitElementOrTemplate( - nodeIndex: number, ast: { - hasViewContainer: boolean, - outputs: BoundEventAst[], - directives: DirectiveAst[], - providers: ProviderAst[], - references: ReferenceAst[], - queryMatches: QueryMatch[] - }, - context: {elementDepth: number}): { + private _visitElementOrTemplate(nodeIndex: number, ast: { + hasViewContainer: boolean, + outputs: BoundEventAst[], + directives: DirectiveAst[], + providers: ProviderAst[], + references: ReferenceAst[], + queryMatches: QueryMatch[] + }): { flags: number, usedEvents: [string, string][], queryMatchesExpr: o.Expression, @@ -317,19 +330,24 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter }); const hostBindings: {value: AST, context: o.Expression}[] = []; const hostEvents: {context: o.Expression, eventAst: BoundEventAst}[] = []; + const componentFactoryResolverProvider = createComponentFactoryResolver(ast.directives); + if (componentFactoryResolverProvider) { + this._visitProvider(componentFactoryResolverProvider, ast.queryMatches); + } + ast.providers.forEach((providerAst, providerIndex) => { let dirAst: DirectiveAst; let dirIndex: number; ast.directives.forEach((localDirAst, i) => { - if (localDirAst.directive.type.reference === providerAst.token.identifier.reference) { + if (localDirAst.directive.type.reference === tokenReference(providerAst.token)) { dirAst = localDirAst; dirIndex = i; } }); if (dirAst) { const {hostBindings: dirHostBindings, hostEvents: dirHostEvents} = this._visitDirective( - providerAst, dirAst, dirIndex, nodeIndex, context.elementDepth, ast.references, - ast.queryMatches, usedEvents); + providerAst, dirAst, dirIndex, nodeIndex, ast.references, ast.queryMatches, usedEvents, + this.staticQueryIds.get(ast)); hostBindings.push(...dirHostBindings); hostEvents.push(...dirHostEvents); } else { @@ -348,8 +366,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter valueType = viewEngine.QueryValueType.TemplateRef; } if (valueType != null) { - queryMatchExprs.push( - o.literalArr([o.literal(calcQueryId(match.query)), o.literal(valueType)])); + queryMatchExprs.push(o.literalArr([o.literal(match.queryId), o.literal(valueType)])); } }); ast.references.forEach((ref) => { @@ -361,7 +378,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter } if (valueType != null) { 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( @@ -383,8 +400,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter private _visitDirective( providerAst: ProviderAst, directiveAst: DirectiveAst, directiveIndex: number, - elementNodeIndex: number, elementDepth: number, refs: ReferenceAst[], - queryMatches: QueryMatch[], usedEvents: Map): { + elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[], + usedEvents: Map, queryIds: StaticAndDynamicQueryIds): { hostBindings: {value: AST, context: o.Expression}[], 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 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) => { - 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 = query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All; 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))]) ])); }); @@ -414,8 +453,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter refs.forEach((ref) => { if (ref.value && tokenReference(ref.value) === tokenReference(providerAst.token)) { this.refNodeIndices[ref.name] = nodeIndex; - queryMatchExprs.push(o.literalArr( - [o.literal(`#${ref.name}`), o.literal(viewEngine.QueryValueType.Provider)])); + queryMatchExprs.push( + o.literalArr([o.literal(ref.name), o.literal(viewEngine.QueryValueType.Provider)])); } }); @@ -505,6 +544,9 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter if (!providerAst.eager) { flags |= viewEngine.NodeFlags.LazyProvider; } + if (providerAst.providerType === ProviderAstType.PrivateService) { + flags |= viewEngine.NodeFlags.PrivateProvider; + } providerAst.lifecycleHooks.forEach((lifecycleHook) => { // for regular providers, we only support ngOnDestroy if (lifecycleHook === LifecycleHooks.OnDestroy || @@ -518,7 +560,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter queryMatches.forEach((match) => { if (tokenReference(match.value) === tokenReference(providerAst.token)) { 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); @@ -680,18 +722,15 @@ function multiProviderDef(providers: CompileProviderMetadata[]): const allDepDefs: o.Expression[] = []; const allParams: o.FnParam[] = []; 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; if (provider.useClass) { + const depExprs = convertDeps(providerIndex, provider.deps || provider.useClass.diDeps); expr = o.importExpr(provider.useClass).instantiate(depExprs); } else if (provider.useFactory) { + const depExprs = convertDeps(providerIndex, provider.deps || provider.useFactory.diDeps); expr = o.importExpr(provider.useFactory).callFn(depExprs); } else if (provider.useExisting) { + const depExprs = convertDeps(providerIndex, [{token: provider.useExisting}]); expr = depExprs[0]; } else { expr = convertValueToOutputAst(provider.useValue); @@ -704,6 +743,15 @@ function multiProviderDef(providers: CompileProviderMetadata[]): providerType: viewEngine.ProviderType.Factory, 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): @@ -765,15 +813,6 @@ function needsAdditionalRootNode(ast: TemplateAst): boolean { 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 { let nodeFlag = viewEngine.NodeFlags.None; switch (lifecycleHook) { @@ -872,4 +911,66 @@ function callCheckStmt(nodeIndex: number, exprs: o.Expression[]): o.Expression { function callUnwrapValue(expr: o.Expression): o.Expression { return o.importExpr(createIdentifier(Identifiers.unwrapValue)).callFn([expr]); +} + +interface StaticAndDynamicQueryIds { + staticQueryIds: Set; + dynamicQueryIds: Set; +} + + +function findStaticQueryIds( + nodes: TemplateAst[], result = new Map()): + Map { + nodes.forEach((node) => { + const staticQueryIds = new Set(); + const dynamicQueryIds = new Set(); + 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; } \ No newline at end of file diff --git a/modules/@angular/compiler/test/template_parser/template_parser_spec.ts b/modules/@angular/compiler/test/template_parser/template_parser_spec.ts index af2b70a8c7..79a01d502d 100644 --- a/modules/@angular/compiler/test/template_parser/template_parser_spec.ts +++ b/modules/@angular/compiler/test/template_parser/template_parser_spec.ts @@ -155,7 +155,7 @@ export function main() { expectVisitedNode( new class extends NullVisitor{visitDirective(ast: DirectiveAst, context: any): any{return ast;}}, - new DirectiveAst(null, [], [], [], null)); + new DirectiveAst(null, [], [], [], 0, null)); }); it('should visit DirectiveAst', () => { @@ -178,7 +178,7 @@ export function main() { new BoundEventAst('foo', 'bar', 'goo', null, null), new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null), new AttrAst('foo', 'bar', null), new BoundTextAst(null, 0, null), - new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], null), + new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], 0, null), new BoundDirectivePropertyAst('foo', 'bar', null, null) ]; const result = templateVisitAll(visitor, nodes, null); diff --git a/modules/@angular/core/src/view/element.ts b/modules/@angular/core/src/view/element.ts index 56826c5387..3066c94223 100644 --- a/modules/@angular/core/src/view/element.ts +++ b/modules/@angular/core/src/view/element.ts @@ -10,39 +10,39 @@ import {isDevMode} from '../application_ref'; import {SecurityContext} from '../security'; import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData} from './types'; -import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, resolveViewDefinition, sliceErrorStack} from './util'; +import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, sliceErrorStack, splitMatchedQueriesDsl} from './util'; export function anchorDef( - flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, - childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef { - const matchedQueryDefs: {[queryId: string]: QueryValueType} = {}; - if (matchedQueries) { - matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; }); - } + flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][], + ngContentIndex: number, childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef { + const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl); // skip the call to sliceErrorStack itself + the call to this function. const source = isDevMode() ? sliceErrorStack(2, 3) : ''; const template = templateFactory ? resolveViewDefinition(templateFactory) : null; + return { type: NodeType.Element, // will bet set by the view definition index: undefined, reverseChildIndex: undefined, parent: undefined, - childFlags: undefined, - childMatchedQueries: undefined, + renderParent: undefined, bindingIndex: undefined, disposableIndex: undefined, // regular values flags, - matchedQueries: matchedQueryDefs, ngContentIndex, childCount, + childFlags: 0, + childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount, bindings: [], disposableCount: 0, element: { name: undefined, attrs: undefined, - outputs: [], template, + outputs: [], template, source, // will bet set by the view definition - providerIndices: undefined, source, + component: undefined, + publicProviders: undefined, + allProviders: undefined, }, provider: undefined, text: undefined, @@ -53,18 +53,16 @@ export function anchorDef( } export function elementDef( - flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, - childCount: number, name: string, fixedAttrs: {[name: string]: string} = {}, + flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][], + ngContentIndex: number, childCount: number, name: string, + fixedAttrs: {[name: string]: string} = {}, bindings?: ([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] | [BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[], outputs?: (string | [string, string])[]): NodeDef { // skip the call to sliceErrorStack itself + the call to this function. const source = isDevMode() ? sliceErrorStack(2, 3) : ''; - const matchedQueryDefs: {[queryId: string]: QueryValueType} = {}; - if (matchedQueries) { - matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; }); - } + const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl); bindings = bindings || []; const bindingDefs: BindingDef[] = new Array(bindings.length); for (let i = 0; i < bindings.length; i++) { @@ -104,22 +102,24 @@ export function elementDef( index: undefined, reverseChildIndex: undefined, parent: undefined, - childFlags: undefined, - childMatchedQueries: undefined, + renderParent: undefined, bindingIndex: undefined, disposableIndex: undefined, // regular values flags, - matchedQueries: matchedQueryDefs, ngContentIndex, childCount, + childFlags: 0, + childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount, bindings: bindingDefs, disposableCount: outputDefs.length, element: { name, attrs: fixedAttrs, - outputs: outputDefs, + outputs: outputDefs, source, template: undefined, // will bet set by the view definition - providerIndices: undefined, source, + component: undefined, + publicProviders: undefined, + allProviders: undefined, }, provider: undefined, text: undefined, @@ -135,8 +135,6 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El const renderer = view.root.renderer; let el: any; if (view.parent || !rootSelectorOrNode) { - const parentNode = - def.parent != null ? asElementData(view, def.parent).renderElement : renderHost; if (elDef.name) { // TODO(vicb): move the namespace to the node definition const nsAndName = splitNamespace(elDef.name); @@ -144,8 +142,9 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El } else { el = renderer.createComment(''); } - if (parentNode) { - renderer.appendChild(parentNode, el); + const parentEl = getParentRenderElement(view, renderHost, def); + if (parentEl) { + renderer.appendChild(parentEl, el); } } else { el = renderer.selectRootElement(rootSelectorOrNode); diff --git a/modules/@angular/core/src/view/ng_content.ts b/modules/@angular/core/src/view/ng_content.ts index 1edfe5f1e4..7473445d2f 100644 --- a/modules/@angular/core/src/view/ng_content.ts +++ b/modules/@angular/core/src/view/ng_content.ts @@ -7,7 +7,7 @@ */ import {NodeDef, NodeType, ViewData, asElementData} from './types'; -import {RenderNodeAction, visitProjectedRenderNodes} from './util'; +import {RenderNodeAction, getParentRenderElement, visitProjectedRenderNodes} from './util'; export function ngContentDef(ngContentIndex: number, index: number): NodeDef { return { @@ -16,13 +16,16 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef { index: undefined, reverseChildIndex: undefined, parent: undefined, - childFlags: undefined, - childMatchedQueries: undefined, + renderParent: undefined, bindingIndex: undefined, disposableIndex: undefined, // regular values flags: 0, - matchedQueries: {}, ngContentIndex, + childFlags: 0, + childMatchedQueries: 0, + matchedQueries: {}, + matchedQueryIds: 0, + references: {}, ngContentIndex, childCount: 0, bindings: [], disposableCount: 0, @@ -36,11 +39,7 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef { } export function appendNgContent(view: ViewData, renderHost: any, def: NodeDef) { - if (def.ngContentIndex != null) { - // Do nothing if we are reprojected! - return; - } - const parentEl = def.parent != null ? asElementData(view, def.parent).renderElement : renderHost; + const parentEl = getParentRenderElement(view, renderHost, def); if (!parentEl) { // Nothing to do if there is no parent element. return; diff --git a/modules/@angular/core/src/view/provider.ts b/modules/@angular/core/src/view/provider.ts index 1a99d80b9e..72a3f8372b 100644 --- a/modules/@angular/core/src/view/provider.ts +++ b/modules/@angular/core/src/view/provider.ts @@ -15,7 +15,7 @@ import * as v1renderer from '../render/api'; 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 {checkAndUpdateBinding, dispatchEvent, isComponentView, tokenKey, viewParentElIndex} from './util'; +import {checkAndUpdateBinding, dispatchEvent, filterQueryId, isComponentView, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util'; const RendererV1TokenKey = tokenKey(v1renderer.Renderer); const ElementRefTokenKey = tokenKey(ElementRef); @@ -27,8 +27,8 @@ const InjectorRefTokenKey = tokenKey(Injector); const NOT_CREATED = new Object(); export function directiveDef( - flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor: any, - deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]}, + flags: NodeFlags, matchedQueries: [string | number, QueryValueType][], childCount: number, + ctor: any, deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]}, outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef { const bindings: BindingDef[] = []; if (props) { @@ -58,20 +58,17 @@ export function pipeDef(flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | an } export function providerDef( - flags: NodeFlags, matchedQueries: [string, QueryValueType][], type: ProviderType, token: any, - value: any, deps: ([DepFlags, any] | any)[]): NodeDef { + flags: NodeFlags, matchedQueries: [string | number, QueryValueType][], type: ProviderType, + token: any, value: any, deps: ([DepFlags, any] | any)[]): NodeDef { return _def(NodeType.Provider, flags, matchedQueries, 0, type, token, value, deps); } 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, deps: ([DepFlags, any] | any)[], bindings?: BindingDef[], outputs?: DirectiveOutputDef[], component?: () => ViewDefinition): NodeDef { - const matchedQueryDefs: {[queryId: string]: QueryValueType} = {}; - if (matchedQueries) { - matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; }); - } + const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl); if (!outputs) { outputs = []; } @@ -100,13 +97,13 @@ export function _def( index: undefined, reverseChildIndex: undefined, parent: undefined, - childFlags: undefined, - childMatchedQueries: undefined, + renderParent: undefined, bindingIndex: undefined, disposableIndex: undefined, // regular values flags, - matchedQueries: matchedQueryDefs, + childFlags: 0, + childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex: undefined, childCount, bindings, disposableCount: outputs.length, element: undefined, @@ -133,21 +130,26 @@ export function createPipeInstance(view: ViewData, def: NodeDef): any { while (compView.parent && !isComponentView(compView)) { compView = compView.parent; } + // pipes can see the private services of the component + const allowPrivateServices = true; // pipes are always eager and classes! return createClass( - compView.parent, compView.parentIndex, viewParentElIndex(compView), def.provider.value, + compView.parent, viewParentEl(compView), allowPrivateServices, def.provider.value, def.provider.deps); } 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; // 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) { for (let i = 0; i < providerDef.outputs.length; i++) { const output = providerDef.outputs[i]; 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); } } @@ -217,17 +219,21 @@ export function checkAndUpdateDirectiveDynamic(view: ViewData, def: NodeDef, val } 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; let injectable: any; switch (providerDef.type) { 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; 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; case ProviderType.UseExisting: - injectable = resolveDep(view, def.index, def.parent, providerDef.deps[0]); + injectable = resolveDep(view, def.parent, allowPrivateServices, providerDef.deps[0]); break; case ProviderType.Value: injectable = providerDef.value; @@ -237,7 +243,7 @@ function _createProviderInstance(view: ViewData, def: NodeDef): any { } 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; let injectable: any; switch (len) { @@ -245,23 +251,23 @@ function createClass( injectable = new ctor(); break; case 1: - injectable = new ctor(resolveDep(view, requestorNodeIndex, elIndex, deps[0])); + injectable = new ctor(resolveDep(view, elDef, allowPrivateServices, deps[0])); break; case 2: injectable = new ctor( - resolveDep(view, requestorNodeIndex, elIndex, deps[0]), - resolveDep(view, requestorNodeIndex, elIndex, deps[1])); + resolveDep(view, elDef, allowPrivateServices, deps[0]), + resolveDep(view, elDef, allowPrivateServices, deps[1])); break; case 3: injectable = new ctor( - resolveDep(view, requestorNodeIndex, elIndex, deps[0]), - resolveDep(view, requestorNodeIndex, elIndex, deps[1]), - resolveDep(view, requestorNodeIndex, elIndex, deps[2])); + resolveDep(view, elDef, allowPrivateServices, deps[0]), + resolveDep(view, elDef, allowPrivateServices, deps[1]), + resolveDep(view, elDef, allowPrivateServices, deps[2])); break; default: const depValues = new Array(len); 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); } @@ -269,7 +275,7 @@ function createClass( } function callFactory( - view: ViewData, requestorNodeIndex: number, elIndex: number, factory: any, + view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, factory: any, deps: DepDef[]): any { const len = deps.length; let injectable: any; @@ -278,23 +284,23 @@ function callFactory( injectable = factory(); break; case 1: - injectable = factory(resolveDep(view, requestorNodeIndex, elIndex, deps[0])); + injectable = factory(resolveDep(view, elDef, allowPrivateServices, deps[0])); break; case 2: injectable = factory( - resolveDep(view, requestorNodeIndex, elIndex, deps[0]), - resolveDep(view, requestorNodeIndex, elIndex, deps[1])); + resolveDep(view, elDef, allowPrivateServices, deps[0]), + resolveDep(view, elDef, allowPrivateServices, deps[1])); break; case 3: injectable = factory( - resolveDep(view, requestorNodeIndex, elIndex, deps[0]), - resolveDep(view, requestorNodeIndex, elIndex, deps[1]), - resolveDep(view, requestorNodeIndex, elIndex, deps[2])); + resolveDep(view, elDef, allowPrivateServices, deps[0]), + resolveDep(view, elDef, allowPrivateServices, deps[1]), + resolveDep(view, elDef, allowPrivateServices, deps[2])); break; default: const depValues = Array(len); 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); } @@ -302,7 +308,7 @@ function callFactory( } 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 { if (depDef.flags & DepFlags.Value) { return depDef.token; @@ -314,59 +320,64 @@ export function resolveDep( const tokenKey = depDef.tokenKey; if (depDef.flags & DepFlags.SkipSelf) { - requestNodeIndex = null; - elIndex = view.def.nodes[elIndex].parent; - while (elIndex == null && view) { - elIndex = viewParentElIndex(view); - view = view.parent; - } + allowPrivateServices = false; + elDef = elDef.parent; } while (view) { - const elDef = view.def.nodes[elIndex]; - switch (tokenKey) { - case RendererV1TokenKey: { - let compView = view; - while (compView && !isComponentView(compView)) { - compView = compView.parent; - } - const rootRenderer: v1renderer.RootRenderer = - view.root.injector.get(v1renderer.RootRenderer); + if (elDef) { + switch (tokenKey) { + case RendererV1TokenKey: { + let compView = view; + while (compView && !isComponentView(compView)) { + compView = compView.parent; + } + const rootRenderer: v1renderer.RootRenderer = + view.root.injector.get(v1renderer.RootRenderer); - // Note: Don't fill in the styles as they have been installed already! - return rootRenderer.renderComponent(new v1renderer.RenderComponentType( - view.def.component.id, '', 0, view.def.component.encapsulation, [], {})); + // Note: Don't fill in the styles as they have been installed already! + return rootRenderer.renderComponent(new v1renderer.RenderComponentType( + 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; - elIndex = viewParentElIndex(view); + allowPrivateServices = isComponentView(view); + elDef = viewParentEl(view); view = view.parent; } return startView.root.injector.get(depDef.token, notFoundValue); diff --git a/modules/@angular/core/src/view/pure_expression.ts b/modules/@angular/core/src/view/pure_expression.ts index dd72c21d83..318dd1c5a8 100644 --- a/modules/@angular/core/src/view/pure_expression.ts +++ b/modules/@angular/core/src/view/pure_expression.ts @@ -40,13 +40,16 @@ function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]): index: undefined, reverseChildIndex: undefined, parent: undefined, - childFlags: undefined, - childMatchedQueries: undefined, + renderParent: undefined, bindingIndex: undefined, disposableIndex: undefined, // regular values flags: 0, + childFlags: 0, + childMatchedQueries: 0, matchedQueries: {}, + matchedQueryIds: 0, + references: {}, ngContentIndex: undefined, childCount: 0, bindings, disposableCount: 0, diff --git a/modules/@angular/core/src/view/query.ts b/modules/@angular/core/src/view/query.ts index fbd2995953..f86b3e701a 100644 --- a/modules/@angular/core/src/view/query.ts +++ b/modules/@angular/core/src/view/query.ts @@ -13,10 +13,10 @@ import {ViewContainerRef} from '../linker/view_container_ref'; import {createTemplateRef, createViewContainerRef} from './refs'; import {NodeDef, NodeFlags, NodeType, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, asElementData, asProviderData, asQueryList} from './types'; -import {declaredViewContainer, viewParentElIndex} from './util'; +import {declaredViewContainer, filterQueryId, isEmbeddedView, viewParentEl} from './util'; export function queryDef( - flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef { + flags: NodeFlags, id: number, bindings: {[propName: string]: QueryBindingType}): NodeDef { let bindingDefs: QueryBindingDef[] = []; for (let propName in bindings) { const bindingType = bindings[propName]; @@ -29,14 +29,17 @@ export function queryDef( index: undefined, reverseChildIndex: undefined, parent: undefined, - childFlags: undefined, - childMatchedQueries: undefined, + renderParent: undefined, bindingIndex: undefined, disposableIndex: undefined, // regular values flags, + childFlags: 0, + childMatchedQueries: 0, ngContentIndex: undefined, matchedQueries: {}, + matchedQueryIds: 0, + references: {}, childCount: 0, bindings: [], disposableCount: 0, @@ -44,7 +47,7 @@ export function queryDef( provider: undefined, text: undefined, pureExpression: undefined, - query: {id, bindings: bindingDefs}, + query: {id, filterId: filterQueryId(id), bindings: bindingDefs}, ngContent: undefined }; } @@ -53,26 +56,40 @@ export function createQuery(): QueryList { return new QueryList(); } -export function dirtyParentQuery(queryId: string, view: ViewData) { - let elIndex = viewParentElIndex(view); - view = view.parent; - let queryIdx: number; - while (view) { - if (elIndex != null) { - const elementDef = view.def.nodes[elIndex]; - queryIdx = elementDef.element.providerIndices[queryId]; - if (queryIdx != null) { - break; +export function dirtyParentQueries(view: ViewData) { + const queryIds = view.def.nodeMatchedQueries; + while (view.parent && isEmbeddedView(view)) { + let tplDef = view.parentNodeDef; + view = view.parent; + // content queries + const end = tplDef.index + tplDef.childCount; + for (let i = 0; i <= end; i++) { + const nodeDef = view.def.nodes[i]; + if ((nodeDef.flags & NodeFlags.HasContentQuery) && + (nodeDef.flags & NodeFlags.HasDynamicQuery) && + (nodeDef.query.filterId & queryIds) === nodeDef.query.filterId) { + asQueryList(view, i).setDirty(); + } + if ((nodeDef.type === NodeType.Element && i + nodeDef.childCount < tplDef.index) || + !(nodeDef.childFlags & NodeFlags.HasContentQuery) || + !(nodeDef.childFlags & NodeFlags.HasDynamicQuery)) { + // skip elements that don't contain the template element or no query. + i += nodeDef.childCount; } } - elIndex = viewParentElIndex(view); - view = view.parent; } - if (!view) { - throw new Error( - `Illegal State: Tried to dirty parent query ${queryId} but the query could not be found!`); + + // view queries + let compDef = view.parentNodeDef; + view = view.parent; + if (view) { + for (let i = compDef.index + 1; i <= compDef.index + compDef.childCount; i++) { + const nodeDef = view.def.nodes[i]; + if ((nodeDef.flags & NodeFlags.HasViewQuery) && (nodeDef.flags & NodeFlags.HasDynamicQuery)) { + asQueryList(view, i).setDirty(); + } + } } - asQueryList(view, queryIdx).setDirty(); } export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) { @@ -80,76 +97,80 @@ export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) { if (!queryList.dirty) { return; } - const queryId = nodeDef.query.id; - const providerDef = view.def.nodes[nodeDef.parent]; + const providerDef = nodeDef.parent; const providerData = asProviderData(view, providerDef.index); let newValues: any[]; if (nodeDef.flags & NodeFlags.HasContentQuery) { - const elementDef = view.def.nodes[providerDef.parent]; + const elementDef = providerDef.parent; newValues = calcQueryValues( - view, elementDef.index, elementDef.index + elementDef.childCount, queryId, []); + view, elementDef.index, elementDef.index + elementDef.childCount, nodeDef.query, []); } else if (nodeDef.flags & NodeFlags.HasViewQuery) { const compView = providerData.componentView; - newValues = calcQueryValues(compView, 0, compView.def.nodes.length - 1, queryId, []); + newValues = calcQueryValues(compView, 0, compView.def.nodes.length - 1, nodeDef.query, []); } queryList.reset(newValues); - let boundValue: any; const bindings = nodeDef.query.bindings; + let notify = false; for (let i = 0; i < bindings.length; i++) { const binding = bindings[i]; + let boundValue: any; switch (binding.bindingType) { case QueryBindingType.First: boundValue = queryList.first; break; case QueryBindingType.All: boundValue = queryList; + notify = true; break; } providerData.instance[binding.propName] = boundValue; } + if (notify) { + queryList.notifyOnChanges(); + } } function calcQueryValues( - view: ViewData, startIndex: number, endIndex: number, queryId: string, values: any[]): any[] { - const len = view.def.nodes.length; + view: ViewData, startIndex: number, endIndex: number, queryDef: QueryDef, + values: any[]): any[] { for (let i = startIndex; i <= endIndex; i++) { const nodeDef = view.def.nodes[i]; - const value = getQueryValue(view, nodeDef, queryId); - if (value != null) { - // a match - values.push(value); + const valueType = nodeDef.matchedQueries[queryDef.id]; + if (valueType != null) { + values.push(getQueryValue(view, nodeDef, valueType)); } - if (nodeDef.flags & NodeFlags.HasEmbeddedViews && - queryId in nodeDef.element.template.nodeMatchedQueries) { + if (nodeDef.type === NodeType.Element && nodeDef.element.template && + (nodeDef.element.template.nodeMatchedQueries & queryDef.filterId) === queryDef.filterId) { // check embedded views that were attached at the place of their template. const elementData = asElementData(view, i); const embeddedViews = elementData.embeddedViews; - for (let k = 0; k < embeddedViews.length; k++) { - const embeddedView = embeddedViews[k]; - const dvc = declaredViewContainer(embeddedView); - if (dvc && dvc === elementData) { - calcQueryValues(embeddedView, 0, embeddedView.def.nodes.length - 1, queryId, values); + if (embeddedViews) { + for (let k = 0; k < embeddedViews.length; k++) { + const embeddedView = embeddedViews[k]; + const dvc = declaredViewContainer(embeddedView); + if (dvc && dvc === elementData) { + calcQueryValues(embeddedView, 0, embeddedView.def.nodes.length - 1, queryDef, values); + } } } const projectedViews = elementData.projectedViews; if (projectedViews) { for (let k = 0; k < projectedViews.length; k++) { const projectedView = projectedViews[k]; - calcQueryValues(projectedView, 0, projectedView.def.nodes.length - 1, queryId, values); + calcQueryValues(projectedView, 0, projectedView.def.nodes.length - 1, queryDef, values); } } } - if (!(queryId in nodeDef.childMatchedQueries)) { - // If don't check descendants, skip the children. - // Or: no child matches the query, then skip the children as well. + if ((nodeDef.childMatchedQueries & queryDef.filterId) !== queryDef.filterId) { + // if no child matches the query, skip the children. i += nodeDef.childCount; } } return values; } -export function getQueryValue(view: ViewData, nodeDef: NodeDef, queryId: string): any { - const queryValueType = nodeDef.matchedQueries[queryId]; +export function getQueryValue( + view: ViewData, nodeDef: NodeDef, queryValueType: QueryValueType): any { if (queryValueType != null) { // a match let value: any; @@ -164,7 +185,7 @@ export function getQueryValue(view: ViewData, nodeDef: NodeDef, queryId: string) value = createTemplateRef(view, nodeDef); break; case QueryValueType.ViewContainerRef: - value = createViewContainerRef(view, nodeDef.index); + value = createViewContainerRef(view, nodeDef); break; case QueryValueType.Provider: value = asProviderData(view, nodeDef.index).instance; diff --git a/modules/@angular/core/src/view/refs.ts b/modules/@angular/core/src/view/refs.ts index bfa5bbe3b4..01a1ac7a06 100644 --- a/modules/@angular/core/src/view/refs.ts +++ b/modules/@angular/core/src/view/refs.ts @@ -16,7 +16,7 @@ import {EmbeddedViewRef, ViewRef} from '../linker/view_ref'; import {Type} from '../type'; import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types'; -import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentElIndex} from './util'; +import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentEl} from './util'; const EMPTY_CONTEXT = new Object(); @@ -45,18 +45,7 @@ class ComponentFactory_ implements ComponentFactory { injector: Injector, projectableNodes: any[][] = null, rootSelectorOrNode: string|any = null): ComponentRef { const viewDef = resolveViewDefinition(this._viewClass); - let componentNodeIndex: number; - const len = viewDef.nodes.length; - for (let i = 0; i < len; i++) { - const nodeDef = viewDef.nodes[i]; - if (nodeDef.flags & NodeFlags.HasComponent) { - componentNodeIndex = i; - break; - } - } - if (componentNodeIndex == null) { - throw new Error(`Illegal State: Could not find a component in the view definition!`); - } + const componentNodeIndex = viewDef.nodes[0].element.component.index; const view = Services.createRootView( injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT); const component = asProviderData(view, componentNodeIndex).instance; @@ -65,9 +54,14 @@ class ComponentFactory_ implements ComponentFactory { } class ComponentRef_ implements ComponentRef { - constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) {} - get location(): ElementRef { return new ElementRef(asElementData(this._view, 0).renderElement); } - get injector(): Injector { return new Injector_(this._view, 0); } + private _elDef: NodeDef; + constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) { + this._elDef = this._view.def.nodes[0]; + } + get location(): ElementRef { + return new ElementRef(asElementData(this._view, this._elDef.index).renderElement); + } + get injector(): Injector { return new Injector_(this._view, this._elDef); } get instance(): any { return this._component; }; get hostView(): ViewRef { return this._viewRef; }; get changeDetectorRef(): ChangeDetectorRef { return this._viewRef; }; @@ -77,28 +71,28 @@ class ComponentRef_ implements ComponentRef { onDestroy(callback: Function): void { this._viewRef.onDestroy(callback); } } -export function createViewContainerRef(view: ViewData, elIndex: number): ViewContainerRef { - return new ViewContainerRef_(view, elIndex); +export function createViewContainerRef(view: ViewData, elDef: NodeDef): ViewContainerRef { + return new ViewContainerRef_(view, elDef); } class ViewContainerRef_ implements ViewContainerRef { private _data: ElementData; - constructor(private _view: ViewData, private _elIndex: number) { - this._data = asElementData(_view, _elIndex); + constructor(private _view: ViewData, private _elDef: NodeDef) { + this._data = asElementData(_view, _elDef.index); } get element(): ElementRef { return new ElementRef(this._data.renderElement); } - get injector(): Injector { return new Injector_(this._view, this._elIndex); } + get injector(): Injector { return new Injector_(this._view, this._elDef); } get parentInjector(): Injector { let view = this._view; - let elIndex = view.def.nodes[this._elIndex].parent; - while (elIndex == null && view) { - elIndex = viewParentElIndex(view); + let elDef = this._elDef.parent; + while (!elDef && view) { + elDef = viewParentEl(view); view = view.parent; } - return view ? new Injector_(view, elIndex) : this._view.root.injector; + return view ? new Injector_(view, elDef) : this._view.root.injector; } clear(): void { @@ -200,15 +194,15 @@ class TemplateRef_ implements TemplateRef { } } -export function createInjector(view: ViewData, elIndex: number): Injector { - return new Injector_(view, elIndex); +export function createInjector(view: ViewData, elDef: NodeDef): Injector { + return new Injector_(view, elDef); } class Injector_ implements Injector { - constructor(private view: ViewData, private elIndex: number) {} + constructor(private view: ViewData, private elDef: NodeDef) {} get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any { return Services.resolveDep( - this.view, undefined, this.elIndex, - {flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue); + this.view, this.elDef, true, {flags: DepFlags.None, token, tokenKey: tokenKey(token)}, + notFoundValue); } } diff --git a/modules/@angular/core/src/view/services.ts b/modules/@angular/core/src/view/services.ts index 2ec65ef86e..be3b984521 100644 --- a/modules/@angular/core/src/view/services.ts +++ b/modules/@angular/core/src/view/services.ts @@ -16,7 +16,7 @@ import {resolveDep} from './provider'; import {getQueryValue} from './query'; import {createInjector} from './refs'; import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types'; -import {checkBinding, isComponentView, queryIdIsReference, renderNode, viewParentElIndex} from './util'; +import {checkBinding, isComponentView, renderNode, viewParentEl} from './util'; import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view'; import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; @@ -191,10 +191,10 @@ function debugCheckFn( if ((binding.type === BindingType.ElementProperty || binding.type === BindingType.DirectiveProperty) && checkBinding(view, nodeDef, i, value)) { - const elIndex = nodeDef.type === NodeType.Directive ? nodeDef.parent : nodeDef.index; + const elDef = nodeDef.type === NodeType.Directive ? nodeDef.parent : nodeDef; setBindingDebugInfo( - view.root.renderer, asElementData(view, elIndex).renderElement, binding.nonMinifiedName, - value); + view.root.renderer, asElementData(view, elDef.index).renderElement, + binding.nonMinifiedName, value); } } return (delegate)(view, nodeIndex, argStyle, ...givenValues); @@ -303,49 +303,37 @@ class DebugContext_ implements DebugContext { private nodeDef: NodeDef; private elView: ViewData; private elDef: NodeDef; - private compProviderIndex: number; + private compProviderDef: NodeDef; constructor(public view: ViewData, public nodeIndex: number) { if (nodeIndex == null) { this.nodeIndex = 0; } this.nodeDef = view.def.nodes[nodeIndex]; - let elIndex = nodeIndex; + let elDef = this.nodeDef; let elView = view; - while (elIndex != null && view.def.nodes[elIndex].type !== NodeType.Element) { - elIndex = view.def.nodes[elIndex].parent; + while (elDef && elDef.type !== NodeType.Element) { + elDef = elDef.parent; } - if (elIndex == null) { - while (elIndex == null && elView) { - elIndex = viewParentElIndex(elView); + if (!elDef) { + while (!elDef && elView) { + elDef = viewParentEl(elView); elView = elView.parent; } } + this.elDef = elDef; this.elView = elView; - if (elView) { - this.elDef = elView.def.nodes[elIndex]; - - for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) { - const childDef = this.elView.def.nodes[i]; - if (childDef.flags & NodeFlags.HasComponent) { - this.compProviderIndex = i; - break; - } - i += childDef.childCount; - } - } else { - this.elDef = null; - } + this.compProviderDef = elView ? this.elDef.element.component : null; } - get injector(): Injector { return createInjector(this.elView, this.elDef.index); } + get injector(): Injector { return createInjector(this.elView, this.elDef); } get component(): any { - if (this.compProviderIndex != null) { - return asProviderData(this.elView, this.compProviderIndex).instance; + if (this.compProviderDef) { + return asProviderData(this.elView, this.compProviderDef.index).instance; } return this.view.component; } get context(): any { - if (this.compProviderIndex != null) { - return asProviderData(this.elView, this.compProviderIndex).instance; + if (this.compProviderDef) { + return asProviderData(this.elView, this.compProviderDef.index).instance; } return this.view.context; } @@ -385,8 +373,8 @@ class DebugContext_ implements DebugContext { } } get componentRenderElement() { - const view = this.compProviderIndex != null ? - asProviderData(this.elView, this.compProviderIndex).componentView : + const view = this.compProviderDef ? + asProviderData(this.elView, this.compProviderDef.index).componentView : this.view; const elData = findHostElement(view); return elData ? elData.renderElement : undefined; @@ -402,17 +390,14 @@ function findHostElement(view: ViewData): ElementData { view = view.parent; } if (view.parent) { - const hostData = asElementData(view.parent, viewParentElIndex(view)); - return hostData; + return asElementData(view.parent, viewParentEl(view).index); } return undefined; } function collectReferences(view: ViewData, nodeDef: NodeDef, references: {[key: string]: any}) { - for (let queryId in nodeDef.matchedQueries) { - if (queryIdIsReference(queryId)) { - references[queryId.slice(1)] = getQueryValue(view, nodeDef, queryId); - } + for (let refName in nodeDef.references) { + references[refName] = getQueryValue(view, nodeDef, nodeDef.references[refName]); } } diff --git a/modules/@angular/core/src/view/text.ts b/modules/@angular/core/src/view/text.ts index 356e45ef11..53816b2ac6 100644 --- a/modules/@angular/core/src/view/text.ts +++ b/modules/@angular/core/src/view/text.ts @@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref'; import {looseIdentical} from '../facade/lang'; import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types'; -import {checkAndUpdateBinding, sliceErrorStack} from './util'; +import {checkAndUpdateBinding, getParentRenderElement, sliceErrorStack} from './util'; export function textDef(ngContentIndex: number, constants: string[]): NodeDef { // skip the call to sliceErrorStack itself + the call to this function. @@ -31,13 +31,16 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef { index: undefined, reverseChildIndex: undefined, parent: undefined, - childFlags: undefined, - childMatchedQueries: undefined, + renderParent: undefined, bindingIndex: undefined, disposableIndex: undefined, // regular values flags: 0, - matchedQueries: {}, ngContentIndex, + childFlags: 0, + childMatchedQueries: 0, + matchedQueries: {}, + matchedQueryIds: 0, + references: {}, ngContentIndex, childCount: 0, bindings, disposableCount: 0, element: undefined, @@ -50,13 +53,12 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef { } export function createText(view: ViewData, renderHost: any, def: NodeDef): TextData { - const parentNode = - def.parent != null ? asElementData(view, def.parent).renderElement : renderHost; let renderNode: any; const renderer = view.root.renderer; renderNode = renderer.createText(def.text.prefix); - if (parentNode) { - renderer.appendChild(parentNode, renderNode); + const parentEl = getParentRenderElement(view, renderHost, def); + if (parentEl) { + renderer.appendChild(parentEl, renderNode); } return {renderText: renderNode}; } diff --git a/modules/@angular/core/src/view/types.ts b/modules/@angular/core/src/view/types.ts index 137697bb27..083f220c4b 100644 --- a/modules/@angular/core/src/view/types.ts +++ b/modules/@angular/core/src/view/types.ts @@ -43,10 +43,11 @@ export interface ViewDefinition { bindingCount: number; disposableCount: number; /** - * ids of all queries that are matched by one of the nodes. + * Binary or of all query ids that are matched by one of the nodes. * This includes query ids from templates as well. + * Used as a bloom filter. */ - nodeMatchedQueries: {[queryId: string]: boolean}; + nodeMatchedQueries: number; } export type ViewDefinitionFactory = () => ViewDefinition; @@ -94,27 +95,35 @@ export interface NodeDef { index: number; reverseChildIndex: number; flags: NodeFlags; - parent: number; + parent: NodeDef; + renderParent: NodeDef; /** this is checked against NgContentDef.index to find matched nodes */ ngContentIndex: number; /** number of transitive children */ childCount: number; - /** aggregated NodeFlags for all children **/ + /** aggregated NodeFlags for all children (does not include self) **/ childFlags: NodeFlags; bindingIndex: number; bindings: BindingDef[]; disposableIndex: number; disposableCount: number; + /** + * references that the user placed on the element + */ + references: {[refId: string]: QueryValueType}; /** * ids and value types of all queries that are matched by this node. */ - matchedQueries: {[queryId: string]: QueryValueType}; + matchedQueries: {[queryId: number]: QueryValueType}; + /** Binary or of all matched query ids of this node. */ + matchedQueryIds: number; /** - * ids of all queries that are matched by one of the child nodes. + * Binary or of all query ids that are matched by one of the children. * This includes query ids from templates as well. + * Used as a bloom filter. */ - childMatchedQueries: {[queryId: string]: boolean}; + childMatchedQueries: number; element: ElementDef; provider: ProviderDef; text: TextDef; @@ -150,8 +159,11 @@ export enum NodeFlags { HasEmbeddedViews = 1 << 8, HasComponent = 1 << 9, HasContentQuery = 1 << 10, - HasViewQuery = 1 << 11, - LazyProvider = 1 << 12 + HasStaticQuery = 1 << 11, + HasDynamicQuery = 1 << 12, + HasViewQuery = 1 << 13, + LazyProvider = 1 << 14, + PrivateProvider = 1 << 15, } export interface BindingDef { @@ -185,11 +197,17 @@ export interface ElementDef { attrs: {[name: string]: string}; outputs: ElementOutputDef[]; template: ViewDefinition; + component: NodeDef; /** - * visible providers for DI in the view, - * as see from this element. + * visible public providers for DI in the view, + * as see from this element. This does not include private providers. */ - providerIndices: {[tokenKey: string]: number}; + publicProviders: {[tokenKey: string]: NodeDef}; + /** + * same as visiblePublicProviders, but also includes private providers + * that are located on this element. + */ + allProviders: {[tokenKey: string]: NodeDef}; source: string; } @@ -229,7 +247,7 @@ export enum DepFlags { None = 0, SkipSelf = 1 << 0, Optional = 1 << 1, - Value = 2 << 2 + Value = 2 << 2, } export interface DirectiveOutputDef { @@ -251,7 +269,9 @@ export enum PureExpressionType { } export interface QueryDef { - id: string; + id: number; + // variant of the id that can be used to check against NodeDef.matchedQueryIds, ... + filterId: number; bindings: QueryBindingDef[]; } @@ -287,7 +307,7 @@ export interface ViewData { def: ViewDefinition; root: RootData; // index of component provider / anchor. - parentIndex: number; + parentNodeDef: NodeDef; parent: ViewData; component: any; context: any; @@ -431,7 +451,7 @@ export interface Services { moveEmbeddedView(elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData; destroyView(view: ViewData): void; resolveDep( - view: ViewData, requestNodeIndex: number, elIndex: number, depDef: DepDef, + view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef, notFoundValue?: any): any; createDebugContext(view: ViewData, nodeIndex: number): DebugContext; handleEvent: ViewHandleEventFn; diff --git a/modules/@angular/core/src/view/util.ts b/modules/@angular/core/src/view/util.ts index 3288ab0505..e74e6177c5 100644 --- a/modules/@angular/core/src/view/util.ts +++ b/modules/@angular/core/src/view/util.ts @@ -17,7 +17,7 @@ import {ViewRef} from '../linker/view_ref'; import {Renderer} from '../render/api'; import {expressionChangedAfterItHasBeenCheckedError, isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors'; -import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, NodeType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types'; +import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types'; const _tokenKeyCache = new Map(); @@ -85,7 +85,7 @@ export function dispatchEvent( export function declaredViewContainer(view: ViewData): ElementData { if (view.parent) { const parentView = view.parent; - return asElementData(parentView, view.parentIndex); + return asElementData(parentView, view.parentNodeDef.index); } return undefined; } @@ -95,10 +95,10 @@ export function declaredViewContainer(view: ViewData): ElementData { * for embedded views, this is the index of the parent node * that contains the view container. */ -export function viewParentElIndex(view: ViewData): number { +export function viewParentEl(view: ViewData): NodeDef { const parentView = view.parent; if (parentView) { - return parentView.def.nodes[view.parentIndex].parent; + return view.parentNodeDef.parent; } else { return null; } @@ -128,10 +128,6 @@ export function nodeValue(view: ViewData, index: number): any { return undefined; } -export function queryIdIsReference(queryId: string): boolean { - return queryId.startsWith('#'); -} - export function elementEventFullName(target: string, name: string): string { return target ? `${target}:${name}` : name; } @@ -140,6 +136,45 @@ export function isComponentView(view: ViewData): boolean { return view.component === view.context && !!view.parent; } +export function isEmbeddedView(view: ViewData): boolean { + return view.component !== view.context && !!view.parent; +} + +export function filterQueryId(queryId: number): number { + return 1 << (queryId % 32); +} + +export function splitMatchedQueriesDsl(matchedQueriesDsl: [string | number, QueryValueType][]): { + matchedQueries: {[queryId: string]: QueryValueType}, + references: {[refId: string]: QueryValueType}, + matchedQueryIds: number +} { + const matchedQueries: {[queryId: string]: QueryValueType} = {}; + let matchedQueryIds = 0; + const references: {[refId: string]: QueryValueType} = {}; + if (matchedQueriesDsl) { + matchedQueriesDsl.forEach(([queryId, valueType]) => { + if (typeof queryId === 'number') { + matchedQueries[queryId] = valueType; + matchedQueryIds |= filterQueryId(queryId); + } else { + references[queryId] = valueType; + } + }); + } + return {matchedQueries, references, matchedQueryIds}; +} + +export function getParentRenderElement(view: ViewData, renderHost: any, def: NodeDef): any { + let parentEl: any; + if (!def.parent) { + parentEl = renderHost; + } else if (def.renderParent) { + parentEl = asElementData(view, def.renderParent.index).renderElement; + } + return parentEl; +} + const VIEW_DEFINITION_CACHE = new WeakMap(); export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition { @@ -187,9 +222,14 @@ export function visitRootRenderNodes( if (action === RenderNodeAction.RemoveChild) { parentNode = view.root.renderer.parentNode(renderNode(view, view.def.lastRootNode)); } + visitSiblingRenderNodes( + view, action, 0, view.def.nodes.length - 1, parentNode, nextSibling, target); +} - const len = view.def.nodes.length; - for (let i = 0; i < len; i++) { +export function visitSiblingRenderNodes( + view: ViewData, action: RenderNodeAction, startIndex: number, endIndex: number, parentNode: any, + nextSibling: any, target: any[]) { + for (let i = startIndex; i <= endIndex; i++) { const nodeDef = view.def.nodes[i]; if (nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text || nodeDef.type === NodeType.NgContent) { @@ -208,7 +248,7 @@ export function visitProjectedRenderNodes( compView = compView.parent; } const hostView = compView.parent; - const hostElDef = hostView.def.nodes[viewParentElIndex(compView)]; + const hostElDef = viewParentEl(compView); const startIndex = hostElDef.index + 1; const endIndex = hostElDef.index + hostElDef.childCount; for (let i = startIndex; i <= endIndex; i++) { @@ -247,6 +287,11 @@ function visitRenderNode( } } } + if (nodeDef.type === NodeType.Element && !nodeDef.element.name) { + visitSiblingRenderNodes( + view, action, nodeDef.index + 1, nodeDef.index + nodeDef.childCount, parentNode, + nextSibling, target); + } } } diff --git a/modules/@angular/core/src/view/view.ts b/modules/@angular/core/src/view/view.ts index 3a7e8c6deb..5108878ea0 100644 --- a/modules/@angular/core/src/view/view.ts +++ b/modules/@angular/core/src/view/view.ts @@ -16,89 +16,121 @@ import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, import {checkAndUpdateQuery, createQuery, queryDef} from './query'; 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 {checkBindingNoChanges, isComponentView, queryIdIsReference, resolveViewDefinition, viewParentElIndex} from './util'; +import {checkBindingNoChanges, isComponentView, resolveViewDefinition, viewParentEl} from './util'; const NOOP = (): any => undefined; export function viewDef( - flags: ViewFlags, nodesWithoutIndices: NodeDef[], updateDirectives?: ViewUpdateFn, + flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, compId?: string, encapsulation?: ViewEncapsulation, styles?: string[]): ViewDefinition { // 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!`); } - const nodes: NodeDef[] = new Array(nodesWithoutIndices.length); - const reverseChildNodes: NodeDef[] = new Array(nodesWithoutIndices.length); + const reverseChildNodes: NodeDef[] = new Array(nodes.length); let viewBindingCount = 0; let viewDisposableCount = 0; let viewNodeFlags = 0; - let viewMatchedQueries: {[queryId: string]: boolean} = {}; + let viewMatchedQueries = 0; let currentParent: NodeDef = null; + let currentElementHasPublicProviders = false; + let currentElementHasPrivateProviders = false; 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) { - const newParent = nodes[currentParent.parent]; + const newParent = currentParent.parent; if (newParent) { newParent.childFlags |= currentParent.childFlags; - copyQueryMatchesInto(currentParent.childMatchedQueries, newParent.childMatchedQueries); + newParent.childMatchedQueries |= currentParent.childMatchedQueries; } currentParent = newParent; } - const nodeWithoutIndices = nodesWithoutIndices[i]; - const reverseChildIndex = calculateReverseChildIndex( - currentParent, i, nodeWithoutIndices.childCount, nodesWithoutIndices.length); + const node = nodes[i]; + node.index = i; + node.parent = currentParent; + node.bindingIndex = viewBindingCount; + node.disposableIndex = viewDisposableCount; + node.reverseChildIndex = + calculateReverseChildIndex(currentParent, i, node.childCount, nodes.length); - const node = cloneAndModifyNode(nodeWithoutIndices, { - index: i, - parent: currentParent ? currentParent.index : undefined, - bindingIndex: viewBindingCount, - disposableIndex: viewDisposableCount, reverseChildIndex, - }); - if (node.element) { - node.element = cloneAndModifyElement(node.element, { - // 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); + let currentRenderParent: NodeDef; + if (currentParent && + !(currentParent.type === NodeType.Element && currentParent.element.component)) { + // children of components should never be attached! + if (currentParent && currentParent.type === NodeType.Element && !currentParent.element.name) { + currentRenderParent = currentParent.renderParent; + } else { + currentRenderParent = currentParent; } } + 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) { lastRootNode = node; } if (node.type === NodeType.Provider || node.type === NodeType.Directive) { - currentParent.element.providerIndices[node.provider.tokenKey] = i; - } - if (node.query) { - const elementDef = nodes[currentParent.parent]; - elementDef.element.providerIndices[node.query.id] = i; + if (!currentElementHasPublicProviders) { + currentElementHasPublicProviders = true; + // Use protoypical inheritance to not get O(n^2) complexity... + currentParent.element.publicProviders = + 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) { currentParent = node; } } while (currentParent) { - const newParent = nodes[currentParent.parent]; + const newParent = currentParent.parent; if (newParent) { newParent.childFlags |= currentParent.childFlags; - copyQueryMatchesInto(currentParent.childMatchedQueries, newParent.childMatchedQueries); + newParent.childMatchedQueries |= currentParent.childMatchedQueries; } 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( currentParent: NodeDef, i: number, childCount: number, nodeCount: number) { // 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 { // embedded views are seen as siblings to the anchor, so we need // to get the parent of the anchor and use it as parentIndex. - const view = createView(parent.root, parent, anchorDef.index, anchorDef.element.template); + const view = createView(parent.root, parent, anchorDef, anchorDef.element.template); initView(view, parent.component, context); createViewNodes(view); return view; @@ -260,13 +236,13 @@ export function createRootView(root: RootData, def: ViewDefinition, context?: an } 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 disposables = def.disposableCount ? new Array(def.disposableCount) : undefined; const view: ViewData = { def, parent, - parentIndex, + parentNodeDef, context: undefined, component: undefined, nodes, state: ViewState.FirstCheck | ViewState.ChecksEnabled, root, @@ -283,7 +259,7 @@ function initView(view: ViewData, component: any, context: any) { function createViewNodes(view: ViewData) { let renderHost: any; if (isComponentView(view)) { - renderHost = asElementData(view.parent, viewParentElIndex(view)).renderElement; + renderHost = asElementData(view.parent, viewParentEl(view).index).renderElement; } const def = view.def; @@ -316,7 +292,7 @@ function createViewNodes(view: ViewData) { // the component view. Therefore, we create the component view first // and set the ProviderData in ViewData, and then instantiate the provider. const componentView = createView( - view.root, view, nodeDef.index, resolveViewDefinition(nodeDef.provider.component)); + view.root, view, nodeDef, resolveViewDefinition(nodeDef.provider.component)); const providerData = {componentView, instance: undefined}; nodes[i] = providerData as any; 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, // so that e.g. ng-content works 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) { Services.updateDirectives(checkNoChangesNode, view); execEmbeddedViewsAction(view, ViewAction.CheckNoChanges); - execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckNoChanges); + execQueriesAction( + view, NodeFlags.HasContentQuery, NodeFlags.HasDynamicQuery, QueryAction.CheckNoChanges); Services.updateRenderer(checkNoChangesNode, view); execComponentViewsAction(view, ViewAction.CheckNoChanges); - execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckNoChanges); + execQueriesAction( + view, NodeFlags.HasViewQuery, NodeFlags.HasDynamicQuery, QueryAction.CheckNoChanges); } export function checkAndUpdateView(view: ViewData) { Services.updateDirectives(checkAndUpdateNode, view); execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate); - execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckAndUpdate); + execQueriesAction( + view, NodeFlags.HasContentQuery, NodeFlags.HasDynamicQuery, QueryAction.CheckAndUpdate); callLifecycleHooksChildrenFirst( view, NodeFlags.AfterContentChecked | @@ -367,7 +351,8 @@ export function checkAndUpdateView(view: ViewData) { Services.updateRenderer(checkAndUpdateNode, view); execComponentViewsAction(view, ViewAction.CheckAndUpdate); - execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckAndUpdate); + execQueriesAction( + view, NodeFlags.HasViewQuery, NodeFlags.HasDynamicQuery, QueryAction.CheckAndUpdate); callLifecycleHooksChildrenFirst( view, NodeFlags.AfterViewChecked | @@ -571,14 +556,17 @@ enum QueryAction { CheckNoChanges } -function execQueriesAction(view: ViewData, queryFlags: NodeFlags, action: QueryAction) { - if (!(view.def.nodeFlags & queryFlags)) { +function execQueriesAction( + view: ViewData, queryFlags: NodeFlags, staticDynamicQueryFlag: NodeFlags, action: QueryAction) { + if (!(view.def.nodeFlags & queryFlags) || !(view.def.nodeFlags & staticDynamicQueryFlag)) { return; } const nodeCount = view.def.nodes.length; for (let i = 0; i < nodeCount; 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); switch (action) { case QueryAction.CheckAndUpdate: @@ -588,8 +576,9 @@ function execQueriesAction(view: ViewData, queryFlags: NodeFlags, action: QueryA checkNoChangesQuery(view, nodeDef); 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 i += nodeDef.childCount; } diff --git a/modules/@angular/core/src/view/view_attach.ts b/modules/@angular/core/src/view/view_attach.ts index 354be573c1..f8e12dbbba 100644 --- a/modules/@angular/core/src/view/view_attach.ts +++ b/modules/@angular/core/src/view/view_attach.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {dirtyParentQuery} from './query'; +import {dirtyParentQueries} from './query'; import {ElementData, NodeData, NodeDef, NodeFlags, NodeType, ViewData, asElementData, asProviderData, asTextData} from './types'; import {RenderNodeAction, declaredViewContainer, isComponentView, renderNode, rootRenderNodes, visitProjectedRenderNodes, visitRootRenderNodes} from './util'; @@ -25,9 +25,7 @@ export function attachEmbeddedView(elementData: ElementData, viewIndex: number, projectedViews.push(view); } - for (let queryId in view.def.nodeMatchedQueries) { - dirtyParentQuery(queryId, view); - } + dirtyParentQueries(view); const prevView = viewIndex > 0 ? embeddedViews[viewIndex - 1] : null; renderAttachEmbeddedView(elementData, prevView, view); @@ -47,9 +45,7 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex: number): removeFromArray(projectedViews, projectedViews.indexOf(view)); } - for (let queryId in view.def.nodeMatchedQueries) { - dirtyParentQuery(queryId, view); - } + dirtyParentQueries(view); renderDetachEmbeddedView(elementData, view); @@ -69,9 +65,7 @@ export function moveEmbeddedView( // Note: Don't need to change projectedViews as the order in there // as always invalid... - for (let queryId in view.def.nodeMatchedQueries) { - dirtyParentQuery(queryId, view); - } + dirtyParentQueries(view); renderDetachEmbeddedView(elementData, view); const prevView = newViewIndex > 0 ? embeddedViews[newViewIndex - 1] : null; diff --git a/modules/@angular/core/test/linker/entry_components_integration_spec.ts b/modules/@angular/core/test/linker/entry_components_integration_spec.ts index 1b3414af40..c085577961 100644 --- a/modules/@angular/core/test/linker/entry_components_integration_spec.ts +++ b/modules/@angular/core/test/linker/entry_components_integration_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {USE_VIEW_ENGINE} from '@angular/compiler/src/config'; import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver} from '@angular/core'; import {noComponentFactoryError} from '@angular/core/src/linker/component_factory_resolver'; import {TestBed} from '@angular/core/testing'; @@ -14,6 +15,19 @@ import {Console} from '../../src/console'; export function main() { + describe('Current compiler', () => { createTests({viewEngine: false}); }); + + describe('View Engine compiler', () => { + beforeEach(() => { + TestBed.configureCompiler( + {useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]}); + }); + + createTests({viewEngine: true}); + }); +} + +function createTests({viewEngine}: {viewEngine: boolean}) { describe('jit', () => { declareTests({useJit: true}); }); describe('no jit', () => { declareTests({useJit: false}); }); } diff --git a/modules/@angular/core/test/linker/integration_spec.ts b/modules/@angular/core/test/linker/integration_spec.ts index 52a50419bf..5527373114 100644 --- a/modules/@angular/core/test/linker/integration_spec.ts +++ b/modules/@angular/core/test/linker/integration_spec.ts @@ -1250,7 +1250,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea .toThrowError(`Directive ${stringify(SomeDirective)} has no selector, please add it!`); }); - viewEngine || it('should use a default element name for components without selectors', () => { + it('should use a default element name for components without selectors', () => { let noSelectorComponentFactory: ComponentFactory; @Component({template: '----'}) diff --git a/modules/@angular/core/test/linker/projection_integration_spec.ts b/modules/@angular/core/test/linker/projection_integration_spec.ts index a71bb25357..9fc489d63a 100644 --- a/modules/@angular/core/test/linker/projection_integration_spec.ts +++ b/modules/@angular/core/test/linker/projection_integration_spec.ts @@ -6,14 +6,27 @@ * found in the LICENSE file at https://angular.io/license */ +import {USE_VIEW_ENGINE} from '@angular/compiler/src/config'; import {Component, Directive, ElementRef, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; import {TestBed} from '@angular/core/testing'; -import {beforeEach, ddescribe, describe, iit, it} from '@angular/core/testing/testing_internal'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {expect} from '@angular/platform-browser/testing/matchers'; export function main() { + describe('Current compiler', () => { createTests({viewEngine: false}); }); + + describe('View Engine compiler', () => { + beforeEach(() => { + TestBed.configureCompiler( + {useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]}); + }); + + createTests({viewEngine: true}); + }); +} + +function createTests({viewEngine}: {viewEngine: boolean}) { describe('projection', () => { beforeEach(() => TestBed.configureTestingModule({declarations: [MainComp, OtherComp, Simple]})); @@ -365,7 +378,7 @@ export function main() { expect(main.nativeElement).toHaveText('TREE(0:TREE2(1:TREE(2:)))'); }); - if (getDOM().supportsNativeShadowDOM()) { + if (!viewEngine && getDOM().supportsNativeShadowDOM()) { it('should support native content projection and isolate styles per component', () => { TestBed.configureTestingModule({declarations: [SimpleNative1, SimpleNative2]}); TestBed.overrideComponent(MainComp, { @@ -383,7 +396,7 @@ export function main() { }); } - if (getDOM().supportsDOMEvents()) { + if (!viewEngine && getDOM().supportsDOMEvents()) { it('should support non emulated styles', () => { TestBed.configureTestingModule({declarations: [OtherComp]}); TestBed.overrideComponent(MainComp, { diff --git a/modules/@angular/core/test/linker/query_integration_spec.ts b/modules/@angular/core/test/linker/query_integration_spec.ts index dad0e322ec..2a22599190 100644 --- a/modules/@angular/core/test/linker/query_integration_spec.ts +++ b/modules/@angular/core/test/linker/query_integration_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {USE_VIEW_ENGINE} from '@angular/compiler/src/config'; import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, asNativeElements} from '@angular/core'; import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/matchers'; @@ -13,6 +14,19 @@ import {expect} from '@angular/platform-browser/testing/matchers'; import {stringify} from '../../src/facade/lang'; export function main() { + describe('Current compiler', () => { createTests({viewEngine: false}); }); + + describe('View Engine compiler', () => { + beforeEach(() => { + TestBed.configureCompiler( + {useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]}); + }); + + createTests({viewEngine: true}); + }); +} + +function createTests({viewEngine}: {viewEngine: boolean}) { describe('Query API', () => { beforeEach(() => TestBed.configureTestingModule({ @@ -267,9 +281,10 @@ export function main() { it('should contain the first descendant content child templateRef', () => { const template = '' + ''; - 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'); }); diff --git a/modules/@angular/core/test/linker/view_injector_integration_spec.ts b/modules/@angular/core/test/linker/view_injector_integration_spec.ts index 20a51d393d..acb46de1ab 100644 --- a/modules/@angular/core/test/linker/view_injector_integration_spec.ts +++ b/modules/@angular/core/test/linker/view_injector_integration_spec.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ +import {USE_VIEW_ENGINE} from '@angular/compiler/src/config'; import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, ElementRef, Host, Inject, Input, Optional, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewContainerRef} from '@angular/core'; import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing'; -import {beforeEach, beforeEachProviders, describe, it} from '@angular/core/testing/testing_internal'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {expect} from '@angular/platform-browser/testing/matchers'; @@ -190,6 +190,19 @@ class TestComp { } export function main() { + describe('Current compiler', () => { createTests({viewEngine: false}); }); + + describe('View Engine compiler', () => { + beforeEach(() => { + TestBed.configureCompiler( + {useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]}); + }); + + createTests({viewEngine: true}); + }); +} + +function createTests({viewEngine}: {viewEngine: boolean}) { function createComponentFixture( template: string, providers: Provider[] = null, comp: Type = null): ComponentFixture { if (!comp) { @@ -213,9 +226,10 @@ export function main() { // On CJS fakeAsync is not supported... if (!getDOM().supportsDOMEvents()) return; - beforeEach(() => TestBed.configureTestingModule({declarations: [TestComp]})); - - beforeEachProviders(() => [{provide: 'appService', useValue: 'appService'}]); + beforeEach(() => TestBed.configureTestingModule({ + declarations: [TestComp], + providers: [{provide: 'appService', useValue: 'appService'}] + })); describe('injection', () => { it('should instantiate directives that have no dependencies', () => { @@ -591,20 +605,19 @@ export function main() { .toBe(el.children[0].nativeElement); }); - it('should inject ChangeDetectorRef of the component\'s view into the component via a proxy', - () => { - TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]}); - const cf = createComponentFixture('
'); - cf.detectChanges(); - const compEl = cf.debugElement.children[0]; - const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); - comp.counter = 1; - cf.detectChanges(); - expect(compEl.nativeElement).toHaveText('0'); - comp.changeDetectorRef.markForCheck(); - cf.detectChanges(); - expect(compEl.nativeElement).toHaveText('1'); - }); + it('should inject ChangeDetectorRef of the component\'s view into the component', () => { + TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]}); + const cf = createComponentFixture('
'); + cf.detectChanges(); + const compEl = cf.debugElement.children[0]; + const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); + comp.counter = 1; + cf.detectChanges(); + expect(compEl.nativeElement).toHaveText('0'); + comp.changeDetectorRef.markForCheck(); + cf.detectChanges(); + expect(compEl.nativeElement).toHaveText('1'); + }); it('should inject ChangeDetectorRef of the containing component into directives', () => { TestBed.configureTestingModule( @@ -624,9 +637,9 @@ export function main() { cf.detectChanges(); expect(compEl.nativeElement).toHaveText('0'); expect(compEl.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef) - .toBe(comp.changeDetectorRef); + .toEqual(comp.changeDetectorRef); expect(compEl.children[1].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef) - .toBe(comp.changeDetectorRef); + .toEqual(comp.changeDetectorRef); comp.changeDetectorRef.markForCheck(); cf.detectChanges(); expect(compEl.nativeElement).toHaveText('1'); @@ -687,7 +700,7 @@ export function main() { '
'); const cdRef = el.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef; - expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toBe(cdRef); + expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toEqual(cdRef); }); it('should cache pure pipes', () => { diff --git a/modules/@angular/core/test/view/query_spec.ts b/modules/@angular/core/test/view/query_spec.ts index 599e39e56c..2eeb37cdcd 100644 --- a/modules/@angular/core/test/view/query_spec.ts +++ b/modules/@angular/core/test/view/query_spec.ts @@ -33,6 +33,8 @@ export function main() { return {rootNodes, view}; } + const someQueryId = 1; + class AService {} class QueryService { @@ -42,19 +44,24 @@ export function main() { function contentQueryProviders() { return [ directiveDef(NodeFlags.None, null, 1, QueryService, []), - queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All}) + queryDef( + NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId, + {'a': QueryBindingType.All}) ]; } function viewQueryProviders(compView: ViewDefinition) { return [ directiveDef(NodeFlags.None, null, 1, QueryService, [], null, null, () => compView), - queryDef(NodeFlags.HasViewQuery, 'query1', {'a': QueryBindingType.All}) + queryDef( + NodeFlags.HasViewQuery | NodeFlags.HasDynamicQuery, someQueryId, + {'a': QueryBindingType.All}) ]; } function aServiceProvider() { - return directiveDef(NodeFlags.None, [['query1', QueryValueType.Provider]], 0, AService, []); + return directiveDef( + NodeFlags.None, [[someQueryId, QueryValueType.Provider]], 0, AService, []); } describe('content queries', () => { @@ -251,7 +258,9 @@ export function main() { const {view} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 4, 'div'), directiveDef(NodeFlags.None, null, 1, QueryService, []), - queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All}), + queryDef( + NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId, + {'a': QueryBindingType.All}), aServiceProvider(), aServiceProvider(), ])); @@ -274,7 +283,9 @@ export function main() { const {view} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 4, 'div'), directiveDef(NodeFlags.None, null, 1, QueryService, []), - queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}), + queryDef( + NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId, + {'a': QueryBindingType.First}), aServiceProvider(), aServiceProvider(), ])); @@ -293,9 +304,11 @@ export function main() { } const {view} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, [['query1', QueryValueType.ElementRef]], null, 2, 'div'), + elementDef(NodeFlags.None, [[someQueryId, QueryValueType.ElementRef]], null, 2, 'div'), directiveDef(NodeFlags.None, null, 1, QueryService, []), - queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}), + queryDef( + NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId, + {'a': QueryBindingType.First}), ])); Services.checkAndUpdateView(view); @@ -311,10 +324,12 @@ export function main() { const {view} = createAndGetRootNodes(compViewDef([ anchorDef( - NodeFlags.None, [['query1', QueryValueType.TemplateRef]], null, 2, + NodeFlags.None, [[someQueryId, QueryValueType.TemplateRef]], null, 2, embeddedViewDef([anchorDef(NodeFlags.None, null, null, 0)])), directiveDef(NodeFlags.None, null, 1, QueryService, []), - queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}), + queryDef( + NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId, + {'a': QueryBindingType.First}), ])); Services.checkAndUpdateView(view); @@ -329,9 +344,11 @@ export function main() { } const {view} = createAndGetRootNodes(compViewDef([ - anchorDef(NodeFlags.None, [['query1', QueryValueType.ViewContainerRef]], null, 2), + anchorDef(NodeFlags.None, [[someQueryId, QueryValueType.ViewContainerRef]], null, 2), directiveDef(NodeFlags.None, null, 1, QueryService, []), - queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}), + queryDef( + NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId, + {'a': QueryBindingType.First}), ])); Services.checkAndUpdateView(view); @@ -367,7 +384,7 @@ export function main() { expect(err).toBeTruthy(); expect(err.message) .toBe( - `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'Query query1 not dirty'. Current value: 'Query query1 dirty'.`); + `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'Query 1 not dirty'. Current value: 'Query 1 dirty'.`); const debugCtx = getDebugContext(err); expect(debugCtx.view).toBe(view); expect(debugCtx.nodeIndex).toBe(2); @@ -381,7 +398,9 @@ export function main() { const {view} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 3, 'div'), directiveDef(NodeFlags.None, null, 1, QueryService, []), - queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All}), + queryDef( + NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId, + {'a': QueryBindingType.All}), aServiceProvider(), ])); diff --git a/modules/@angular/core/test/view/services_spec.ts b/modules/@angular/core/test/view/services_spec.ts index f705c73821..198884874d 100644 --- a/modules/@angular/core/test/view/services_spec.ts +++ b/modules/@angular/core/test/view/services_spec.ts @@ -39,7 +39,7 @@ export function main() { directiveDef( NodeFlags.None, null, 0, AComp, [], null, null, () => compViewDef([ - elementDef(NodeFlags.None, [['#ref', QueryValueType.ElementRef]], null, 2, 'span'), + elementDef(NodeFlags.None, [['ref', QueryValueType.ElementRef]], null, 2, 'span'), directiveDef(NodeFlags.None, null, 0, AService, []), textDef(null, ['a']) ])), ])); diff --git a/modules/@angular/core/test/view/view_def_spec.ts b/modules/@angular/core/test/view/view_def_spec.ts index 4183843ec8..67e9f21e07 100644 --- a/modules/@angular/core/test/view/view_def_spec.ts +++ b/modules/@angular/core/test/view/view_def_spec.ts @@ -7,6 +7,7 @@ */ import {NodeFlags, QueryValueType, ViewData, ViewDefinition, ViewFlags, anchorDef, directiveDef, elementDef, textDef, viewDef} from '@angular/core/src/view/index'; +import {filterQueryId} from '@angular/core/src/view/util'; export function main() { describe('viewDef', () => { @@ -76,7 +77,7 @@ export function main() { describe('parent', () => { function parents(viewDef: ViewDefinition): number[] { - return viewDef.nodes.map(node => node.parent); + return viewDef.nodes.map(node => node.parent ? node.parent.index : null); } it('should calculate parents for one level', () => { @@ -86,7 +87,7 @@ export function main() { textDef(null, ['a']), ]); - expect(parents(vd)).toEqual([undefined, 0, 0]); + expect(parents(vd)).toEqual([null, 0, 0]); }); it('should calculate parents for one level, multiple roots', () => { @@ -98,7 +99,7 @@ export function main() { textDef(null, ['a']), ]); - expect(parents(vd)).toEqual([undefined, 0, undefined, 2, undefined]); + expect(parents(vd)).toEqual([null, 0, null, 2, null]); }); it('should calculate parents for multiple levels', () => { @@ -111,7 +112,7 @@ export function main() { textDef(null, ['a']), ]); - expect(parents(vd)).toEqual([undefined, 0, 1, undefined, 3, undefined]); + expect(parents(vd)).toEqual([null, 0, 1, null, 3, null]); }); }); @@ -175,52 +176,56 @@ export function main() { }); describe('childMatchedQueries', () => { - function childMatchedQueries(viewDef: ViewDefinition): string[][] { - return viewDef.nodes.map(node => Object.keys(node.childMatchedQueries).sort()); + function childMatchedQueries(viewDef: ViewDefinition): number[] { + return viewDef.nodes.map(node => node.childMatchedQueries); } it('should calculate childMatchedQueries for one level', () => { const vd = viewDef(ViewFlags.None, [ elementDef(NodeFlags.None, null, null, 1, 'span'), - directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, []) + directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, []) ]); - expect(childMatchedQueries(vd)).toEqual([['q1'], []]); + expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), 0]); }); it('should calculate childMatchedQueries for two levels', () => { const vd = viewDef(ViewFlags.None, [ elementDef(NodeFlags.None, null, null, 2, 'span'), elementDef(NodeFlags.None, null, null, 1, 'span'), - directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, []) + directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, []) ]); - expect(childMatchedQueries(vd)).toEqual([['q1'], ['q1'], []]); + expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), filterQueryId(1), 0]); }); it('should calculate childMatchedQueries for one level, multiple roots', () => { const vd = viewDef(ViewFlags.None, [ elementDef(NodeFlags.None, null, null, 1, 'span'), - directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, []), + directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, []), elementDef(NodeFlags.None, null, null, 2, 'span'), - directiveDef(NodeFlags.None, [['q2', QueryValueType.Provider]], 0, AService, []), - directiveDef(NodeFlags.None, [['q3', QueryValueType.Provider]], 0, AService, []), + directiveDef(NodeFlags.None, [[2, QueryValueType.Provider]], 0, AService, []), + directiveDef(NodeFlags.None, [[3, QueryValueType.Provider]], 0, AService, []), ]); - expect(childMatchedQueries(vd)).toEqual([['q1'], [], ['q2', 'q3'], [], []]); + expect(childMatchedQueries(vd)).toEqual([ + filterQueryId(1), 0, filterQueryId(2) | filterQueryId(3), 0, 0 + ]); }); it('should calculate childMatchedQueries for multiple levels', () => { const vd = viewDef(ViewFlags.None, [ elementDef(NodeFlags.None, null, null, 2, 'span'), elementDef(NodeFlags.None, null, null, 1, 'span'), - directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, []), + directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, []), elementDef(NodeFlags.None, null, null, 2, 'span'), - directiveDef(NodeFlags.None, [['q2', QueryValueType.Provider]], 0, AService, []), - directiveDef(NodeFlags.None, [['q3', QueryValueType.Provider]], 0, AService, []), + directiveDef(NodeFlags.None, [[2, QueryValueType.Provider]], 0, AService, []), + directiveDef(NodeFlags.None, [[3, QueryValueType.Provider]], 0, AService, []), ]); - expect(childMatchedQueries(vd)).toEqual([['q1'], ['q1'], [], ['q2', 'q3'], [], []]); + expect(childMatchedQueries(vd)).toEqual([ + filterQueryId(1), filterQueryId(1), 0, filterQueryId(2) | filterQueryId(3), 0, 0 + ]); }); it('should included embedded views into childMatchedQueries', () => { @@ -231,12 +236,12 @@ export function main() { () => viewDef( ViewFlags.None, [ - elementDef(NodeFlags.None, [['q1', QueryValueType.Provider]], null, 0, 'span'), + elementDef(NodeFlags.None, [[1, QueryValueType.Provider]], null, 0, 'span'), ])) ]); // Note: the template will become a sibling to the anchor once stamped out, - expect(childMatchedQueries(vd)).toEqual([['q1'], []]); + expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), 0]); }); }); }); diff --git a/modules/@angular/platform-browser/src/dom/dom_renderer.ts b/modules/@angular/platform-browser/src/dom/dom_renderer.ts index f0b818faf1..dd8a131cbc 100644 --- a/modules/@angular/platform-browser/src/dom/dom_renderer.ts +++ b/modules/@angular/platform-browser/src/dom/dom_renderer.ts @@ -389,7 +389,11 @@ export class DomRendererV2 implements RendererV2 { } } - removeChild(parent: any, oldChild: any): void { parent.removeChild(oldChild); } + removeChild(parent: any, oldChild: any): void { + if (parent) { + parent.removeChild(oldChild); + } + } selectRootElement(selectorOrNode: string|any, debugInfo?: any): any { let el: any = typeof selectorOrNode === 'string' ? document.querySelector(selectorOrNode) : diff --git a/modules/@angular/platform-server/src/server_renderer.ts b/modules/@angular/platform-server/src/server_renderer.ts index ff86a9508d..4d2bd0aaf1 100644 --- a/modules/@angular/platform-server/src/server_renderer.ts +++ b/modules/@angular/platform-server/src/server_renderer.ts @@ -259,7 +259,11 @@ export class ServerRendererV2 implements RendererV2 { } } - removeChild(parent: any, oldChild: any): void { getDOM().removeChild(parent, oldChild); } + removeChild(parent: any, oldChild: any): void { + if (parent) { + getDOM().removeChild(parent, oldChild); + } + } selectRootElement(selectorOrNode: string|any, debugInfo?: any): any { let el: any;