feat(compiler): integrate compiler with view engine (#14487)
Aspects: di, query, content projection Included refactoring: - use a number as query id - use a bloom filter for aggregating matched queries of nested elements - separate static vs dynamic queries Part of #14013
This commit is contained in:
parent
e9ba7aa4f8
commit
4e7752a12a
|
@ -304,6 +304,7 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
|
|||
providers: CompileProviderMetadata[];
|
||||
viewProviders: CompileProviderMetadata[];
|
||||
queries: CompileQueryMetadata[];
|
||||
viewQueries: CompileQueryMetadata[];
|
||||
entryComponents: CompileEntryComponentMetadata[];
|
||||
changeDetection: ChangeDetectionStrategy;
|
||||
template: CompileTemplateSummary;
|
||||
|
@ -480,6 +481,7 @@ export class CompileDirectiveMetadata {
|
|||
providers: this.providers,
|
||||
viewProviders: this.viewProviders,
|
||||
queries: this.queries,
|
||||
viewQueries: this.viewQueries,
|
||||
entryComponents: this.entryComponents,
|
||||
changeDetection: this.changeDetection,
|
||||
template: this.template && this.template.toSummary(),
|
||||
|
|
|
@ -368,6 +368,12 @@ export class Identifiers {
|
|||
{name: 'ɵviewEngine', moduleUrl: CORE, member: 'pipeDef', runtime: ɵviewEngine.pipeDef};
|
||||
static nodeValue: IdentifierSpec =
|
||||
{name: 'ɵviewEngine', moduleUrl: CORE, member: 'nodeValue', runtime: ɵviewEngine.nodeValue};
|
||||
static ngContentDef: IdentifierSpec = {
|
||||
name: 'ɵviewEngine',
|
||||
moduleUrl: CORE,
|
||||
member: 'ngContentDef',
|
||||
runtime: ɵviewEngine.ngContentDef
|
||||
};
|
||||
static unwrapValue: IdentifierSpec = {
|
||||
name: 'ɵviewEngine',
|
||||
moduleUrl: CORE,
|
||||
|
|
|
@ -11,7 +11,7 @@ import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveS
|
|||
import {isBlank, isPresent} from './facade/lang';
|
||||
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||
import {ParseError, ParseSourceSpan} from './parse_util';
|
||||
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, QueryId, QueryMatch, ReferenceAst} from './template_parser/template_ast';
|
||||
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst} from './template_parser/template_ast';
|
||||
|
||||
export class ProviderError extends ParseError {
|
||||
constructor(message: string, span: ParseSourceSpan) { super(span, message); }
|
||||
|
@ -19,7 +19,7 @@ export class ProviderError extends ParseError {
|
|||
|
||||
export interface QueryWithId {
|
||||
meta: CompileQueryMetadata;
|
||||
id: QueryId;
|
||||
queryId: number;
|
||||
}
|
||||
|
||||
export class ProviderViewContext {
|
||||
|
@ -57,16 +57,21 @@ export class ProviderElementContext {
|
|||
constructor(
|
||||
public viewContext: ProviderViewContext, private _parent: ProviderElementContext,
|
||||
private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[],
|
||||
refs: ReferenceAst[], private _sourceSpan: ParseSourceSpan) {
|
||||
refs: ReferenceAst[], isTemplate: boolean, contentQueryStartId: number,
|
||||
private _sourceSpan: ParseSourceSpan) {
|
||||
this._attrs = {};
|
||||
attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value);
|
||||
const directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive);
|
||||
this._allProviders =
|
||||
_resolveProvidersFromDirectives(directivesMeta, _sourceSpan, viewContext.errors);
|
||||
this._contentQueries = _getContentQueries(this.depth, directivesMeta);
|
||||
this._contentQueries = _getContentQueries(contentQueryStartId, directivesMeta);
|
||||
Array.from(this._allProviders.values()).forEach((provider) => {
|
||||
this._addQueryReadsTo(provider.token, provider.token, this._queriedTokens);
|
||||
});
|
||||
if (isTemplate) {
|
||||
const templateRefId = createIdentifierToken(Identifiers.TemplateRef);
|
||||
this._addQueryReadsTo(templateRefId, templateRefId, this._queriedTokens);
|
||||
}
|
||||
refs.forEach((refAst) => {
|
||||
let defaultQueryValue = refAst.value || createIdentifierToken(Identifiers.ElementRef);
|
||||
this._addQueryReadsTo({value: refAst.name}, defaultQueryValue, this._queriedTokens);
|
||||
|
@ -91,16 +96,6 @@ export class ProviderElementContext {
|
|||
});
|
||||
}
|
||||
|
||||
get depth(): number {
|
||||
let d = 0;
|
||||
let current: ProviderElementContext = this;
|
||||
while (current._parent) {
|
||||
d++;
|
||||
current = current._parent;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
get transformProviders(): ProviderAst[] {
|
||||
return Array.from(this._transformedProviders.values());
|
||||
}
|
||||
|
@ -133,7 +128,7 @@ export class ProviderElementContext {
|
|||
queryMatches = [];
|
||||
queryReadTokens.set(tokenRef, queryMatches);
|
||||
}
|
||||
queryMatches.push({query: query.id, value: queryValue});
|
||||
queryMatches.push({queryId: query.queryId, value: queryValue});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -483,24 +478,24 @@ function _resolveProviders(
|
|||
|
||||
|
||||
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, QueryWithId[]> {
|
||||
// Note: queries start with id 1 so we can use the number in a Bloom filter!
|
||||
let viewQueryId = 1;
|
||||
const viewQueries = new Map<any, QueryWithId[]>();
|
||||
if (component.viewQueries) {
|
||||
component.viewQueries.forEach(
|
||||
(query, queryIndex) => _addQueryToTokenMap(
|
||||
viewQueries,
|
||||
{meta: query, id: {elementDepth: null, directiveIndex: null, queryIndex: queryIndex}}));
|
||||
(query) => _addQueryToTokenMap(viewQueries, {meta: query, queryId: viewQueryId++}));
|
||||
}
|
||||
return viewQueries;
|
||||
}
|
||||
|
||||
function _getContentQueries(
|
||||
elementDepth: number, directives: CompileDirectiveSummary[]): Map<any, QueryWithId[]> {
|
||||
contentQueryStartId: number, directives: CompileDirectiveSummary[]): Map<any, QueryWithId[]> {
|
||||
let contentQueryId = contentQueryStartId;
|
||||
const contentQueries = new Map<any, QueryWithId[]>();
|
||||
directives.forEach((directive, directiveIndex) => {
|
||||
if (directive.queries) {
|
||||
directive.queries.forEach(
|
||||
(query, queryIndex) => _addQueryToTokenMap(
|
||||
contentQueries, {meta: query, id: {elementDepth, directiveIndex, queryIndex}}));
|
||||
(query) => _addQueryToTokenMap(contentQueries, {meta: query, queryId: contentQueryId++}));
|
||||
}
|
||||
});
|
||||
return contentQueries;
|
||||
|
|
|
@ -170,7 +170,7 @@ export class DirectiveAst implements TemplateAst {
|
|||
constructor(
|
||||
public directive: CompileDirectiveSummary, public inputs: BoundDirectivePropertyAst[],
|
||||
public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[],
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
public contentQueryStartId: number, public sourceSpan: ParseSourceSpan) {}
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitDirective(this, context);
|
||||
}
|
||||
|
@ -241,17 +241,8 @@ export enum PropertyBindingType {
|
|||
Animation
|
||||
}
|
||||
|
||||
/**
|
||||
* This id differentiates a query on an element from any query on any child.
|
||||
*/
|
||||
export interface QueryId {
|
||||
elementDepth: number;
|
||||
directiveIndex: number;
|
||||
queryIndex: number;
|
||||
}
|
||||
|
||||
export interface QueryMatch {
|
||||
query: QueryId;
|
||||
queryId: number;
|
||||
value: CompileTokenMetadata;
|
||||
}
|
||||
|
||||
|
|
|
@ -207,12 +207,15 @@ export class TemplateParser {
|
|||
class TemplateParseVisitor implements html.Visitor {
|
||||
selectorMatcher = new SelectorMatcher();
|
||||
directivesIndex = new Map<CompileDirectiveSummary, number>();
|
||||
ngContentCount: number = 0;
|
||||
ngContentCount = 0;
|
||||
contentQueryStartId: number;
|
||||
|
||||
constructor(
|
||||
public providerViewContext: ProviderViewContext, directives: CompileDirectiveSummary[],
|
||||
private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry,
|
||||
private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) {
|
||||
// Note: queries start with id 1 so we can use the number in a Bloom filter!
|
||||
this.contentQueryStartId = providerViewContext.component.viewQueries.length + 1;
|
||||
directives.forEach((directive, index) => {
|
||||
const selector = CssSelector.parse(directive.selector);
|
||||
this.selectorMatcher.addSelectables(selector, directive);
|
||||
|
@ -241,6 +244,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||
visitComment(comment: html.Comment, context: any): any { return null; }
|
||||
|
||||
visitElement(element: html.Element, parent: ElementContext): any {
|
||||
const queryStartIndex = this.contentQueryStartId;
|
||||
const nodeName = element.name;
|
||||
const preparsedElement = preparseElement(element);
|
||||
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
||||
|
@ -322,9 +326,9 @@ class TemplateParseVisitor implements html.Visitor {
|
|||
|
||||
const providerContext = new ProviderElementContext(
|
||||
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
|
||||
references, element.sourceSpan);
|
||||
references, isTemplateElement, queryStartIndex, element.sourceSpan);
|
||||
|
||||
const children = html.visitAll(
|
||||
const children: TemplateAst[] = html.visitAll(
|
||||
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
|
||||
ElementContext.create(
|
||||
isTemplateElement, directiveAsts,
|
||||
|
@ -378,6 +382,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||
}
|
||||
|
||||
if (hasInlineTemplates) {
|
||||
const templateQueryStartIndex = this.contentQueryStartId;
|
||||
const templateCssSelector =
|
||||
createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
|
||||
const {directives: templateDirectiveMetas} =
|
||||
|
@ -392,7 +397,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||
templateDirectiveAsts, templateElementProps, element.sourceSpan);
|
||||
const templateProviderContext = new ProviderElementContext(
|
||||
this.providerViewContext, parent.providerContext, parent.isTemplateElement,
|
||||
templateDirectiveAsts, [], [], element.sourceSpan);
|
||||
templateDirectiveAsts, [], [], true, templateQueryStartIndex, element.sourceSpan);
|
||||
templateProviderContext.afterElement();
|
||||
|
||||
parsedElement = new EmbeddedTemplateAst(
|
||||
|
@ -585,8 +590,11 @@ class TemplateParseVisitor implements html.Visitor {
|
|||
matchedReferences.add(elOrDirRef.name);
|
||||
}
|
||||
});
|
||||
const contentQueryStartId = this.contentQueryStartId;
|
||||
this.contentQueryStartId += directive.queries.length;
|
||||
return new DirectiveAst(
|
||||
directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
|
||||
directive, directiveProperties, hostProperties, hostEvents, contentQueryStartId,
|
||||
sourceSpan);
|
||||
});
|
||||
|
||||
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
||||
|
@ -779,7 +787,7 @@ class NonBindableVisitor implements html.Visitor {
|
|||
const attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]);
|
||||
const selector = createElementCssSelector(ast.name, attrNameAndValues);
|
||||
const ngContentIndex = parent.findNgContentIndex(selector);
|
||||
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
|
||||
const children: TemplateAst[] = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
|
||||
return new ElementAst(
|
||||
ast.name, html.visitAll(this, ast.attrs), [], [], [], [], [], false, [], children,
|
||||
ngContentIndex, ast.sourceSpan, ast.endSourceSpan);
|
||||
|
|
|
@ -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<TemplateAst, StaticAndDynamicQueryIds>,
|
||||
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: {
|
||||
private _visitElementOrTemplate(nodeIndex: number, ast: {
|
||||
hasViewContainer: boolean,
|
||||
outputs: BoundEventAst[],
|
||||
directives: DirectiveAst[],
|
||||
providers: ProviderAst[],
|
||||
references: ReferenceAst[],
|
||||
queryMatches: QueryMatch[]
|
||||
},
|
||||
context: {elementDepth: number}): {
|
||||
}): {
|
||||
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(<any>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<string, any>): {
|
||||
elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[],
|
||||
usedEvents: Map<string, any>, 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) {
|
||||
|
@ -873,3 +912,65 @@ 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<number>;
|
||||
dynamicQueryIds: Set<number>;
|
||||
}
|
||||
|
||||
|
||||
function findStaticQueryIds(
|
||||
nodes: TemplateAst[], result = new Map<TemplateAst, StaticAndDynamicQueryIds>()):
|
||||
Map<TemplateAst, StaticAndDynamicQueryIds> {
|
||||
nodes.forEach((node) => {
|
||||
const staticQueryIds = new Set<number>();
|
||||
const dynamicQueryIds = new Set<number>();
|
||||
let queryMatches: QueryMatch[];
|
||||
if (node instanceof ElementAst) {
|
||||
findStaticQueryIds(node.children, result);
|
||||
node.children.forEach((child) => {
|
||||
const childData = result.get(child);
|
||||
childData.staticQueryIds.forEach(queryId => staticQueryIds.add(queryId));
|
||||
childData.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId));
|
||||
});
|
||||
queryMatches = node.queryMatches;
|
||||
} else if (node instanceof EmbeddedTemplateAst) {
|
||||
findStaticQueryIds(node.children, result);
|
||||
node.children.forEach((child) => {
|
||||
const childData = result.get(child);
|
||||
childData.staticQueryIds.forEach(queryId => dynamicQueryIds.add(queryId));
|
||||
childData.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId));
|
||||
});
|
||||
queryMatches = node.queryMatches;
|
||||
}
|
||||
if (queryMatches) {
|
||||
queryMatches.forEach((match) => staticQueryIds.add(match.queryId));
|
||||
}
|
||||
dynamicQueryIds.forEach(queryId => staticQueryIds.delete(queryId));
|
||||
result.set(node, {staticQueryIds, dynamicQueryIds});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst {
|
||||
const componentDirMeta = directives.find(dirAst => dirAst.directive.isComponent);
|
||||
if (componentDirMeta) {
|
||||
const entryComponentFactories = componentDirMeta.directive.entryComponents.map(
|
||||
(entryComponent) => o.importExpr({reference: entryComponent.componentFactory}));
|
||||
const cfrExpr = o.importExpr(createIdentifier(Identifiers.CodegenComponentFactoryResolver))
|
||||
.instantiate([o.literalArr(entryComponentFactories)]);
|
||||
const token = createIdentifierToken(Identifiers.ComponentFactoryResolver);
|
||||
const classMeta: CompileTypeMetadata = {
|
||||
diDeps: [
|
||||
{isValue: true, value: o.literalArr(entryComponentFactories)},
|
||||
{token: token, isSkipSelf: true, isOptional: true}
|
||||
],
|
||||
lifecycleHooks: [],
|
||||
reference: resolveIdentifier(Identifiers.CodegenComponentFactoryResolver)
|
||||
};
|
||||
return new ProviderAst(
|
||||
token, false, true, [{token, multi: false, useClass: classMeta}],
|
||||
ProviderAstType.PrivateService, [], componentDirMeta.sourceSpan);
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,16 +320,12 @@ 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];
|
||||
if (elDef) {
|
||||
switch (tokenKey) {
|
||||
case RendererV1TokenKey: {
|
||||
let compView = view;
|
||||
|
@ -338,35 +340,44 @@ export function resolveDep(
|
|||
view.def.component.id, '', 0, view.def.component.encapsulation, [], {}));
|
||||
}
|
||||
case ElementRefTokenKey:
|
||||
return new ElementRef(asElementData(view, elIndex).renderElement);
|
||||
return new ElementRef(asElementData(view, elDef.index).renderElement);
|
||||
case ViewContainerRefTokenKey:
|
||||
return createViewContainerRef(view, elIndex);
|
||||
case TemplateRefTokenKey:
|
||||
return createViewContainerRef(view, elDef);
|
||||
case TemplateRefTokenKey: {
|
||||
if (elDef.element.template) {
|
||||
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;
|
||||
}
|
||||
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, elIndex);
|
||||
return createInjector(view, elDef);
|
||||
default:
|
||||
const providerIndex = elDef.element.providerIndices[tokenKey];
|
||||
if (providerIndex != null) {
|
||||
const providerData = asProviderData(view, providerIndex);
|
||||
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, view.def.nodes[providerIndex]);
|
||||
providerData.instance = _createProviderInstance(view, providerDef);
|
||||
}
|
||||
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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -13,10 +13,10 @@ import {ViewContainerRef} from '../linker/view_container_ref';
|
|||
|
||||
import {createTemplateRef, createViewContainerRef} from './refs';
|
||||
import {NodeDef, NodeFlags, NodeType, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, asElementData, asProviderData, asQueryList} from './types';
|
||||
import {declaredViewContainer, viewParentElIndex} from './util';
|
||||
import {declaredViewContainer, filterQueryId, isEmbeddedView, viewParentEl} from './util';
|
||||
|
||||
export function queryDef(
|
||||
flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef {
|
||||
flags: NodeFlags, id: number, bindings: {[propName: string]: QueryBindingType}): NodeDef {
|
||||
let bindingDefs: QueryBindingDef[] = [];
|
||||
for (let propName in bindings) {
|
||||
const bindingType = bindings[propName];
|
||||
|
@ -29,14 +29,17 @@ export function queryDef(
|
|||
index: undefined,
|
||||
reverseChildIndex: undefined,
|
||||
parent: undefined,
|
||||
childFlags: undefined,
|
||||
childMatchedQueries: undefined,
|
||||
renderParent: undefined,
|
||||
bindingIndex: undefined,
|
||||
disposableIndex: undefined,
|
||||
// regular values
|
||||
flags,
|
||||
childFlags: 0,
|
||||
childMatchedQueries: 0,
|
||||
ngContentIndex: undefined,
|
||||
matchedQueries: {},
|
||||
matchedQueryIds: 0,
|
||||
references: {},
|
||||
childCount: 0,
|
||||
bindings: [],
|
||||
disposableCount: 0,
|
||||
|
@ -44,7 +47,7 @@ export function queryDef(
|
|||
provider: undefined,
|
||||
text: undefined,
|
||||
pureExpression: undefined,
|
||||
query: {id, bindings: bindingDefs},
|
||||
query: {id, filterId: filterQueryId(id), bindings: bindingDefs},
|
||||
ngContent: undefined
|
||||
};
|
||||
}
|
||||
|
@ -53,26 +56,40 @@ export function createQuery(): QueryList<any> {
|
|||
return new QueryList();
|
||||
}
|
||||
|
||||
export function dirtyParentQuery(queryId: string, view: ViewData) {
|
||||
let elIndex = viewParentElIndex(view);
|
||||
export function dirtyParentQueries(view: ViewData) {
|
||||
const queryIds = view.def.nodeMatchedQueries;
|
||||
while (view.parent && isEmbeddedView(view)) {
|
||||
let tplDef = view.parentNodeDef;
|
||||
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;
|
||||
// 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 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();
|
||||
}
|
||||
}
|
||||
if (!view) {
|
||||
throw new Error(
|
||||
`Illegal State: Tried to dirty parent query ${queryId} but the query could not be found!`);
|
||||
}
|
||||
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;
|
||||
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, queryId, values);
|
||||
calcQueryValues(embeddedView, 0, embeddedView.def.nodes.length - 1, queryDef, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
const projectedViews = elementData.projectedViews;
|
||||
if (projectedViews) {
|
||||
for (let k = 0; k < projectedViews.length; k++) {
|
||||
const projectedView = projectedViews[k];
|
||||
calcQueryValues(projectedView, 0, projectedView.def.nodes.length - 1, queryId, values);
|
||||
calcQueryValues(projectedView, 0, projectedView.def.nodes.length - 1, queryDef, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(queryId in nodeDef.childMatchedQueries)) {
|
||||
// If don't check descendants, skip the children.
|
||||
// Or: no child matches the query, then skip the children as well.
|
||||
if ((nodeDef.childMatchedQueries & queryDef.filterId) !== queryDef.filterId) {
|
||||
// if no child matches the query, skip the children.
|
||||
i += nodeDef.childCount;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
export function getQueryValue(view: ViewData, nodeDef: NodeDef, queryId: string): any {
|
||||
const queryValueType = <QueryValueType>nodeDef.matchedQueries[queryId];
|
||||
export function getQueryValue(
|
||||
view: ViewData, nodeDef: NodeDef, queryValueType: QueryValueType): any {
|
||||
if (queryValueType != null) {
|
||||
// a match
|
||||
let value: any;
|
||||
|
@ -164,7 +185,7 @@ export function getQueryValue(view: ViewData, nodeDef: NodeDef, queryId: string)
|
|||
value = createTemplateRef(view, nodeDef);
|
||||
break;
|
||||
case QueryValueType.ViewContainerRef:
|
||||
value = createViewContainerRef(view, nodeDef.index);
|
||||
value = createViewContainerRef(view, nodeDef);
|
||||
break;
|
||||
case QueryValueType.Provider:
|
||||
value = asProviderData(view, nodeDef.index).instance;
|
||||
|
|
|
@ -16,7 +16,7 @@ import {EmbeddedViewRef, ViewRef} from '../linker/view_ref';
|
|||
import {Type} from '../type';
|
||||
|
||||
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types';
|
||||
import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentElIndex} from './util';
|
||||
import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentEl} from './util';
|
||||
|
||||
const EMPTY_CONTEXT = new Object();
|
||||
|
||||
|
@ -45,18 +45,7 @@ class ComponentFactory_ implements ComponentFactory<any> {
|
|||
injector: Injector, projectableNodes: any[][] = null,
|
||||
rootSelectorOrNode: string|any = null): ComponentRef<any> {
|
||||
const viewDef = resolveViewDefinition(this._viewClass);
|
||||
let componentNodeIndex: number;
|
||||
const len = viewDef.nodes.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const nodeDef = viewDef.nodes[i];
|
||||
if (nodeDef.flags & NodeFlags.HasComponent) {
|
||||
componentNodeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (componentNodeIndex == null) {
|
||||
throw new Error(`Illegal State: Could not find a component in the view definition!`);
|
||||
}
|
||||
const componentNodeIndex = viewDef.nodes[0].element.component.index;
|
||||
const view = Services.createRootView(
|
||||
injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT);
|
||||
const component = asProviderData(view, componentNodeIndex).instance;
|
||||
|
@ -65,9 +54,14 @@ class ComponentFactory_ implements ComponentFactory<any> {
|
|||
}
|
||||
|
||||
class ComponentRef_ implements ComponentRef<any> {
|
||||
constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) {}
|
||||
get location(): ElementRef { return new ElementRef(asElementData(this._view, 0).renderElement); }
|
||||
get injector(): Injector { return new Injector_(this._view, 0); }
|
||||
private _elDef: NodeDef;
|
||||
constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) {
|
||||
this._elDef = this._view.def.nodes[0];
|
||||
}
|
||||
get location(): ElementRef {
|
||||
return new ElementRef(asElementData(this._view, this._elDef.index).renderElement);
|
||||
}
|
||||
get injector(): Injector { return new Injector_(this._view, this._elDef); }
|
||||
get instance(): any { return this._component; };
|
||||
get hostView(): ViewRef { return this._viewRef; };
|
||||
get changeDetectorRef(): ChangeDetectorRef { return this._viewRef; };
|
||||
|
@ -77,28 +71,28 @@ class ComponentRef_ implements ComponentRef<any> {
|
|||
onDestroy(callback: Function): void { this._viewRef.onDestroy(callback); }
|
||||
}
|
||||
|
||||
export function createViewContainerRef(view: ViewData, elIndex: number): ViewContainerRef {
|
||||
return new ViewContainerRef_(view, elIndex);
|
||||
export function createViewContainerRef(view: ViewData, elDef: NodeDef): ViewContainerRef {
|
||||
return new ViewContainerRef_(view, elDef);
|
||||
}
|
||||
|
||||
class ViewContainerRef_ implements ViewContainerRef {
|
||||
private _data: ElementData;
|
||||
constructor(private _view: ViewData, private _elIndex: number) {
|
||||
this._data = asElementData(_view, _elIndex);
|
||||
constructor(private _view: ViewData, private _elDef: NodeDef) {
|
||||
this._data = asElementData(_view, _elDef.index);
|
||||
}
|
||||
|
||||
get element(): ElementRef { return new ElementRef(this._data.renderElement); }
|
||||
|
||||
get injector(): Injector { return new Injector_(this._view, this._elIndex); }
|
||||
get injector(): Injector { return new Injector_(this._view, this._elDef); }
|
||||
|
||||
get parentInjector(): Injector {
|
||||
let view = this._view;
|
||||
let elIndex = view.def.nodes[this._elIndex].parent;
|
||||
while (elIndex == null && view) {
|
||||
elIndex = viewParentElIndex(view);
|
||||
let elDef = this._elDef.parent;
|
||||
while (!elDef && view) {
|
||||
elDef = viewParentEl(view);
|
||||
view = view.parent;
|
||||
}
|
||||
return view ? new Injector_(view, elIndex) : this._view.root.injector;
|
||||
return view ? new Injector_(view, elDef) : this._view.root.injector;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
|
@ -200,15 +194,15 @@ class TemplateRef_ implements TemplateRef<any> {
|
|||
}
|
||||
}
|
||||
|
||||
export function createInjector(view: ViewData, elIndex: number): Injector {
|
||||
return new Injector_(view, elIndex);
|
||||
export function createInjector(view: ViewData, elDef: NodeDef): Injector {
|
||||
return new Injector_(view, elDef);
|
||||
}
|
||||
|
||||
class Injector_ implements Injector {
|
||||
constructor(private view: ViewData, private elIndex: number) {}
|
||||
constructor(private view: ViewData, private elDef: NodeDef) {}
|
||||
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
|
||||
return Services.resolveDep(
|
||||
this.view, undefined, this.elIndex,
|
||||
{flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue);
|
||||
this.view, this.elDef, true, {flags: DepFlags.None, token, tokenKey: tokenKey(token)},
|
||||
notFoundValue);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import {resolveDep} from './provider';
|
|||
import {getQueryValue} from './query';
|
||||
import {createInjector} from './refs';
|
||||
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types';
|
||||
import {checkBinding, isComponentView, queryIdIsReference, renderNode, viewParentElIndex} from './util';
|
||||
import {checkBinding, isComponentView, renderNode, viewParentEl} from './util';
|
||||
import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view';
|
||||
import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';
|
||||
|
||||
|
@ -191,10 +191,10 @@ function debugCheckFn(
|
|||
if ((binding.type === BindingType.ElementProperty ||
|
||||
binding.type === BindingType.DirectiveProperty) &&
|
||||
checkBinding(view, nodeDef, i, value)) {
|
||||
const elIndex = nodeDef.type === NodeType.Directive ? nodeDef.parent : nodeDef.index;
|
||||
const elDef = nodeDef.type === NodeType.Directive ? nodeDef.parent : nodeDef;
|
||||
setBindingDebugInfo(
|
||||
view.root.renderer, asElementData(view, elIndex).renderElement, binding.nonMinifiedName,
|
||||
value);
|
||||
view.root.renderer, asElementData(view, elDef.index).renderElement,
|
||||
binding.nonMinifiedName, value);
|
||||
}
|
||||
}
|
||||
return (<any>delegate)(view, nodeIndex, argStyle, ...givenValues);
|
||||
|
@ -303,49 +303,37 @@ class DebugContext_ implements DebugContext {
|
|||
private nodeDef: NodeDef;
|
||||
private elView: ViewData;
|
||||
private elDef: NodeDef;
|
||||
private compProviderIndex: number;
|
||||
private compProviderDef: NodeDef;
|
||||
constructor(public view: ViewData, public nodeIndex: number) {
|
||||
if (nodeIndex == null) {
|
||||
this.nodeIndex = 0;
|
||||
}
|
||||
this.nodeDef = view.def.nodes[nodeIndex];
|
||||
let elIndex = nodeIndex;
|
||||
let elDef = this.nodeDef;
|
||||
let elView = view;
|
||||
while (elIndex != null && view.def.nodes[elIndex].type !== NodeType.Element) {
|
||||
elIndex = view.def.nodes[elIndex].parent;
|
||||
while (elDef && elDef.type !== NodeType.Element) {
|
||||
elDef = elDef.parent;
|
||||
}
|
||||
if (elIndex == null) {
|
||||
while (elIndex == null && elView) {
|
||||
elIndex = viewParentElIndex(elView);
|
||||
if (!elDef) {
|
||||
while (!elDef && elView) {
|
||||
elDef = viewParentEl(elView);
|
||||
elView = elView.parent;
|
||||
}
|
||||
}
|
||||
this.elDef = elDef;
|
||||
this.elView = elView;
|
||||
if (elView) {
|
||||
this.elDef = elView.def.nodes[elIndex];
|
||||
|
||||
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
|
||||
const childDef = this.elView.def.nodes[i];
|
||||
if (childDef.flags & NodeFlags.HasComponent) {
|
||||
this.compProviderIndex = i;
|
||||
break;
|
||||
this.compProviderDef = elView ? this.elDef.element.component : null;
|
||||
}
|
||||
i += childDef.childCount;
|
||||
}
|
||||
} else {
|
||||
this.elDef = null;
|
||||
}
|
||||
}
|
||||
get injector(): Injector { return createInjector(this.elView, this.elDef.index); }
|
||||
get injector(): Injector { return createInjector(this.elView, this.elDef); }
|
||||
get component(): any {
|
||||
if (this.compProviderIndex != null) {
|
||||
return asProviderData(this.elView, this.compProviderIndex).instance;
|
||||
if (this.compProviderDef) {
|
||||
return asProviderData(this.elView, this.compProviderDef.index).instance;
|
||||
}
|
||||
return this.view.component;
|
||||
}
|
||||
get context(): any {
|
||||
if (this.compProviderIndex != null) {
|
||||
return asProviderData(this.elView, this.compProviderIndex).instance;
|
||||
if (this.compProviderDef) {
|
||||
return asProviderData(this.elView, this.compProviderDef.index).instance;
|
||||
}
|
||||
return this.view.context;
|
||||
}
|
||||
|
@ -385,8 +373,8 @@ class DebugContext_ implements DebugContext {
|
|||
}
|
||||
}
|
||||
get componentRenderElement() {
|
||||
const view = this.compProviderIndex != null ?
|
||||
asProviderData(this.elView, this.compProviderIndex).componentView :
|
||||
const view = this.compProviderDef ?
|
||||
asProviderData(this.elView, this.compProviderDef.index).componentView :
|
||||
this.view;
|
||||
const elData = findHostElement(view);
|
||||
return elData ? elData.renderElement : undefined;
|
||||
|
@ -402,17 +390,14 @@ function findHostElement(view: ViewData): ElementData {
|
|||
view = view.parent;
|
||||
}
|
||||
if (view.parent) {
|
||||
const hostData = asElementData(view.parent, viewParentElIndex(view));
|
||||
return hostData;
|
||||
return asElementData(view.parent, viewParentEl(view).index);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function collectReferences(view: ViewData, nodeDef: NodeDef, references: {[key: string]: any}) {
|
||||
for (let queryId in nodeDef.matchedQueries) {
|
||||
if (queryIdIsReference(queryId)) {
|
||||
references[queryId.slice(1)] = getQueryValue(view, nodeDef, queryId);
|
||||
}
|
||||
for (let refName in nodeDef.references) {
|
||||
references[refName] = getQueryValue(view, nodeDef, nodeDef.references[refName]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,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};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -17,7 +17,7 @@ import {ViewRef} from '../linker/view_ref';
|
|||
import {Renderer} from '../render/api';
|
||||
|
||||
import {expressionChangedAfterItHasBeenCheckedError, isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
|
||||
import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, NodeType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types';
|
||||
import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types';
|
||||
|
||||
const _tokenKeyCache = new Map<any, string>();
|
||||
|
||||
|
@ -85,7 +85,7 @@ export function dispatchEvent(
|
|||
export function declaredViewContainer(view: ViewData): ElementData {
|
||||
if (view.parent) {
|
||||
const parentView = view.parent;
|
||||
return asElementData(parentView, view.parentIndex);
|
||||
return asElementData(parentView, view.parentNodeDef.index);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
@ -95,10 +95,10 @@ export function declaredViewContainer(view: ViewData): ElementData {
|
|||
* for embedded views, this is the index of the parent node
|
||||
* that contains the view container.
|
||||
*/
|
||||
export function viewParentElIndex(view: ViewData): number {
|
||||
export function viewParentEl(view: ViewData): NodeDef {
|
||||
const parentView = view.parent;
|
||||
if (parentView) {
|
||||
return parentView.def.nodes[view.parentIndex].parent;
|
||||
return view.parentNodeDef.parent;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -128,10 +128,6 @@ export function nodeValue(view: ViewData, index: number): any {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
export function queryIdIsReference(queryId: string): boolean {
|
||||
return queryId.startsWith('#');
|
||||
}
|
||||
|
||||
export function elementEventFullName(target: string, name: string): string {
|
||||
return target ? `${target}:${name}` : name;
|
||||
}
|
||||
|
@ -140,6 +136,45 @@ export function isComponentView(view: ViewData): boolean {
|
|||
return view.component === view.context && !!view.parent;
|
||||
}
|
||||
|
||||
export function isEmbeddedView(view: ViewData): boolean {
|
||||
return view.component !== view.context && !!view.parent;
|
||||
}
|
||||
|
||||
export function filterQueryId(queryId: number): number {
|
||||
return 1 << (queryId % 32);
|
||||
}
|
||||
|
||||
export function splitMatchedQueriesDsl(matchedQueriesDsl: [string | number, QueryValueType][]): {
|
||||
matchedQueries: {[queryId: string]: QueryValueType},
|
||||
references: {[refId: string]: QueryValueType},
|
||||
matchedQueryIds: number
|
||||
} {
|
||||
const matchedQueries: {[queryId: string]: QueryValueType} = {};
|
||||
let matchedQueryIds = 0;
|
||||
const references: {[refId: string]: QueryValueType} = {};
|
||||
if (matchedQueriesDsl) {
|
||||
matchedQueriesDsl.forEach(([queryId, valueType]) => {
|
||||
if (typeof queryId === 'number') {
|
||||
matchedQueries[queryId] = valueType;
|
||||
matchedQueryIds |= filterQueryId(queryId);
|
||||
} else {
|
||||
references[queryId] = valueType;
|
||||
}
|
||||
});
|
||||
}
|
||||
return {matchedQueries, references, matchedQueryIds};
|
||||
}
|
||||
|
||||
export function getParentRenderElement(view: ViewData, renderHost: any, def: NodeDef): any {
|
||||
let parentEl: any;
|
||||
if (!def.parent) {
|
||||
parentEl = renderHost;
|
||||
} else if (def.renderParent) {
|
||||
parentEl = asElementData(view, def.renderParent.index).renderElement;
|
||||
}
|
||||
return parentEl;
|
||||
}
|
||||
|
||||
const VIEW_DEFINITION_CACHE = new WeakMap<any, ViewDefinition>();
|
||||
|
||||
export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition {
|
||||
|
@ -187,9 +222,14 @@ export function visitRootRenderNodes(
|
|||
if (action === RenderNodeAction.RemoveChild) {
|
||||
parentNode = view.root.renderer.parentNode(renderNode(view, view.def.lastRootNode));
|
||||
}
|
||||
visitSiblingRenderNodes(
|
||||
view, action, 0, view.def.nodes.length - 1, parentNode, nextSibling, target);
|
||||
}
|
||||
|
||||
const len = view.def.nodes.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
export function visitSiblingRenderNodes(
|
||||
view: ViewData, action: RenderNodeAction, startIndex: number, endIndex: number, parentNode: any,
|
||||
nextSibling: any, target: any[]) {
|
||||
for (let i = startIndex; i <= endIndex; i++) {
|
||||
const nodeDef = view.def.nodes[i];
|
||||
if (nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text ||
|
||||
nodeDef.type === NodeType.NgContent) {
|
||||
|
@ -208,7 +248,7 @@ export function visitProjectedRenderNodes(
|
|||
compView = compView.parent;
|
||||
}
|
||||
const hostView = compView.parent;
|
||||
const hostElDef = hostView.def.nodes[viewParentElIndex(compView)];
|
||||
const hostElDef = viewParentEl(compView);
|
||||
const startIndex = hostElDef.index + 1;
|
||||
const endIndex = hostElDef.index + hostElDef.childCount;
|
||||
for (let i = startIndex; i <= endIndex; i++) {
|
||||
|
@ -247,6 +287,11 @@ function visitRenderNode(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (nodeDef.type === NodeType.Element && !nodeDef.element.name) {
|
||||
visitSiblingRenderNodes(
|
||||
view, action, nodeDef.index + 1, nodeDef.index + nodeDef.childCount, parentNode,
|
||||
nextSibling, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
});
|
||||
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;
|
||||
}
|
||||
nodes[i] = node;
|
||||
reverseChildNodes[reverseChildIndex] = node;
|
||||
validateNode(currentParent, node, nodesWithoutIndices.length);
|
||||
}
|
||||
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;
|
||||
copyQueryMatchesInto(node.matchedQueries, viewMatchedQueries);
|
||||
viewBindingCount += node.bindings.length;
|
||||
viewDisposableCount += node.disposableCount;
|
||||
viewMatchedQueries |= node.matchedQueryIds;
|
||||
if (node.element && node.element.template) {
|
||||
viewMatchedQueries |= node.element.template.nodeMatchedQueries;
|
||||
}
|
||||
if (currentParent) {
|
||||
currentParent.childFlags |= node.flags;
|
||||
copyQueryMatchesInto(node.matchedQueries, currentParent.childMatchedQueries);
|
||||
currentParent.childMatchedQueries |= node.matchedQueryIds;
|
||||
if (node.element && node.element.template) {
|
||||
copyQueryMatchesInto(
|
||||
node.element.template.nodeMatchedQueries, currentParent.childMatchedQueries);
|
||||
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 (!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.query) {
|
||||
const elementDef = nodes[currentParent.parent];
|
||||
elementDef.element.providerIndices[node.query.id] = i;
|
||||
}
|
||||
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 = <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;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {dirtyParentQuery} from './query';
|
||||
import {dirtyParentQueries} from './query';
|
||||
import {ElementData, NodeData, NodeDef, NodeFlags, NodeType, ViewData, asElementData, asProviderData, asTextData} from './types';
|
||||
import {RenderNodeAction, declaredViewContainer, isComponentView, renderNode, rootRenderNodes, visitProjectedRenderNodes, visitRootRenderNodes} from './util';
|
||||
|
||||
|
@ -25,9 +25,7 @@ export function attachEmbeddedView(elementData: ElementData, viewIndex: number,
|
|||
projectedViews.push(view);
|
||||
}
|
||||
|
||||
for (let queryId in view.def.nodeMatchedQueries) {
|
||||
dirtyParentQuery(queryId, view);
|
||||
}
|
||||
dirtyParentQueries(view);
|
||||
|
||||
const prevView = viewIndex > 0 ? embeddedViews[viewIndex - 1] : null;
|
||||
renderAttachEmbeddedView(elementData, prevView, view);
|
||||
|
@ -47,9 +45,7 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex: number):
|
|||
removeFromArray(projectedViews, projectedViews.indexOf(view));
|
||||
}
|
||||
|
||||
for (let queryId in view.def.nodeMatchedQueries) {
|
||||
dirtyParentQuery(queryId, view);
|
||||
}
|
||||
dirtyParentQueries(view);
|
||||
|
||||
renderDetachEmbeddedView(elementData, view);
|
||||
|
||||
|
@ -69,9 +65,7 @@ export function moveEmbeddedView(
|
|||
// Note: Don't need to change projectedViews as the order in there
|
||||
// as always invalid...
|
||||
|
||||
for (let queryId in view.def.nodeMatchedQueries) {
|
||||
dirtyParentQuery(queryId, view);
|
||||
}
|
||||
dirtyParentQueries(view);
|
||||
|
||||
renderDetachEmbeddedView(elementData, view);
|
||||
const prevView = newViewIndex > 0 ? embeddedViews[newViewIndex - 1] : null;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver} from '@angular/core';
|
||||
import {noComponentFactoryError} from '@angular/core/src/linker/component_factory_resolver';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
@ -14,6 +15,19 @@ import {Console} from '../../src/console';
|
|||
|
||||
|
||||
export function main() {
|
||||
describe('Current compiler', () => { createTests({viewEngine: false}); });
|
||||
|
||||
describe('View Engine compiler', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureCompiler(
|
||||
{useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]});
|
||||
});
|
||||
|
||||
createTests({viewEngine: true});
|
||||
});
|
||||
}
|
||||
|
||||
function createTests({viewEngine}: {viewEngine: boolean}) {
|
||||
describe('jit', () => { declareTests({useJit: true}); });
|
||||
describe('no jit', () => { declareTests({useJit: false}); });
|
||||
}
|
||||
|
|
|
@ -1250,7 +1250,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
|
|||
.toThrowError(`Directive ${stringify(SomeDirective)} has no selector, please add it!`);
|
||||
});
|
||||
|
||||
viewEngine || it('should use a default element name for components without selectors', () => {
|
||||
it('should use a default element name for components without selectors', () => {
|
||||
let noSelectorComponentFactory: ComponentFactory<SomeComponent>;
|
||||
|
||||
@Component({template: '----'})
|
||||
|
|
|
@ -6,14 +6,27 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
|
||||
import {Component, Directive, ElementRef, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {beforeEach, ddescribe, describe, iit, it} from '@angular/core/testing/testing_internal';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('Current compiler', () => { createTests({viewEngine: false}); });
|
||||
|
||||
describe('View Engine compiler', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureCompiler(
|
||||
{useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]});
|
||||
});
|
||||
|
||||
createTests({viewEngine: true});
|
||||
});
|
||||
}
|
||||
|
||||
function createTests({viewEngine}: {viewEngine: boolean}) {
|
||||
describe('projection', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({declarations: [MainComp, OtherComp, Simple]}));
|
||||
|
||||
|
@ -365,7 +378,7 @@ export function main() {
|
|||
expect(main.nativeElement).toHaveText('TREE(0:TREE2(1:TREE(2:)))');
|
||||
});
|
||||
|
||||
if (getDOM().supportsNativeShadowDOM()) {
|
||||
if (!viewEngine && getDOM().supportsNativeShadowDOM()) {
|
||||
it('should support native content projection and isolate styles per component', () => {
|
||||
TestBed.configureTestingModule({declarations: [SimpleNative1, SimpleNative2]});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
|
@ -383,7 +396,7 @@ export function main() {
|
|||
});
|
||||
}
|
||||
|
||||
if (getDOM().supportsDOMEvents()) {
|
||||
if (!viewEngine && getDOM().supportsDOMEvents()) {
|
||||
it('should support non emulated styles', () => {
|
||||
TestBed.configureTestingModule({declarations: [OtherComp]});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
|
||||
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, asNativeElements} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
@ -13,6 +14,19 @@ import {expect} from '@angular/platform-browser/testing/matchers';
|
|||
import {stringify} from '../../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
describe('Current compiler', () => { createTests({viewEngine: false}); });
|
||||
|
||||
describe('View Engine compiler', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureCompiler(
|
||||
{useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]});
|
||||
});
|
||||
|
||||
createTests({viewEngine: true});
|
||||
});
|
||||
}
|
||||
|
||||
function createTests({viewEngine}: {viewEngine: boolean}) {
|
||||
describe('Query API', () => {
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
|
@ -267,9 +281,10 @@ export function main() {
|
|||
it('should contain the first descendant content child templateRef', () => {
|
||||
const template = '<needs-content-child-template-ref-app>' +
|
||||
'</needs-content-child-template-ref-app>';
|
||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||
const view = createTestCmp(MyComp0, template);
|
||||
|
||||
view.detectChanges();
|
||||
// can't execute checkNoChanges as our view modifies our content children (via a query).
|
||||
view.detectChanges(false);
|
||||
expect(view.nativeElement).toHaveText('OUTER');
|
||||
});
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
|
||||
import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, ElementRef, Host, Inject, Input, Optional, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewContainerRef} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
|
||||
import {beforeEach, beforeEachProviders, describe, it} from '@angular/core/testing/testing_internal';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
|
@ -190,6 +190,19 @@ class TestComp {
|
|||
}
|
||||
|
||||
export function main() {
|
||||
describe('Current compiler', () => { createTests({viewEngine: false}); });
|
||||
|
||||
describe('View Engine compiler', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureCompiler(
|
||||
{useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]});
|
||||
});
|
||||
|
||||
createTests({viewEngine: true});
|
||||
});
|
||||
}
|
||||
|
||||
function createTests({viewEngine}: {viewEngine: boolean}) {
|
||||
function createComponentFixture<T>(
|
||||
template: string, providers: Provider[] = null, comp: Type<T> = null): ComponentFixture<T> {
|
||||
if (!comp) {
|
||||
|
@ -213,9 +226,10 @@ export function main() {
|
|||
// On CJS fakeAsync is not supported...
|
||||
if (!getDOM().supportsDOMEvents()) return;
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({declarations: [TestComp]}));
|
||||
|
||||
beforeEachProviders(() => [{provide: 'appService', useValue: 'appService'}]);
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
declarations: [TestComp],
|
||||
providers: [{provide: 'appService', useValue: 'appService'}]
|
||||
}));
|
||||
|
||||
describe('injection', () => {
|
||||
it('should instantiate directives that have no dependencies', () => {
|
||||
|
@ -591,8 +605,7 @@ export function main() {
|
|||
.toBe(el.children[0].nativeElement);
|
||||
});
|
||||
|
||||
it('should inject ChangeDetectorRef of the component\'s view into the component via a proxy',
|
||||
() => {
|
||||
it('should inject ChangeDetectorRef of the component\'s view into the component', () => {
|
||||
TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]});
|
||||
const cf = createComponentFixture('<div componentNeedsChangeDetectorRef></div>');
|
||||
cf.detectChanges();
|
||||
|
@ -624,9 +637,9 @@ export function main() {
|
|||
cf.detectChanges();
|
||||
expect(compEl.nativeElement).toHaveText('0');
|
||||
expect(compEl.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef)
|
||||
.toBe(comp.changeDetectorRef);
|
||||
.toEqual(comp.changeDetectorRef);
|
||||
expect(compEl.children[1].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef)
|
||||
.toBe(comp.changeDetectorRef);
|
||||
.toEqual(comp.changeDetectorRef);
|
||||
comp.changeDetectorRef.markForCheck();
|
||||
cf.detectChanges();
|
||||
expect(compEl.nativeElement).toHaveText('1');
|
||||
|
@ -687,7 +700,7 @@ export function main() {
|
|||
'<div [simpleDirective]="true | pipeNeedsChangeDetectorRef" directiveNeedsChangeDetectorRef></div>');
|
||||
const cdRef =
|
||||
el.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef;
|
||||
expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toBe(cdRef);
|
||||
expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toEqual(cdRef);
|
||||
});
|
||||
|
||||
it('should cache pure pipes', () => {
|
||||
|
|
|
@ -33,6 +33,8 @@ export function main() {
|
|||
return {rootNodes, view};
|
||||
}
|
||||
|
||||
const someQueryId = 1;
|
||||
|
||||
class AService {}
|
||||
|
||||
class QueryService {
|
||||
|
@ -42,19 +44,24 @@ export function main() {
|
|||
function contentQueryProviders() {
|
||||
return [
|
||||
directiveDef(NodeFlags.None, null, 1, QueryService, []),
|
||||
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All})
|
||||
queryDef(
|
||||
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
|
||||
{'a': QueryBindingType.All})
|
||||
];
|
||||
}
|
||||
|
||||
function viewQueryProviders(compView: ViewDefinition) {
|
||||
return [
|
||||
directiveDef(NodeFlags.None, null, 1, QueryService, [], null, null, () => compView),
|
||||
queryDef(NodeFlags.HasViewQuery, 'query1', {'a': QueryBindingType.All})
|
||||
queryDef(
|
||||
NodeFlags.HasViewQuery | NodeFlags.HasDynamicQuery, someQueryId,
|
||||
{'a': QueryBindingType.All})
|
||||
];
|
||||
}
|
||||
|
||||
function aServiceProvider() {
|
||||
return directiveDef(NodeFlags.None, [['query1', QueryValueType.Provider]], 0, AService, []);
|
||||
return directiveDef(
|
||||
NodeFlags.None, [[someQueryId, QueryValueType.Provider]], 0, AService, []);
|
||||
}
|
||||
|
||||
describe('content queries', () => {
|
||||
|
@ -251,7 +258,9 @@ export function main() {
|
|||
const {view} = createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, null, null, 4, 'div'),
|
||||
directiveDef(NodeFlags.None, null, 1, QueryService, []),
|
||||
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All}),
|
||||
queryDef(
|
||||
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
|
||||
{'a': QueryBindingType.All}),
|
||||
aServiceProvider(),
|
||||
aServiceProvider(),
|
||||
]));
|
||||
|
@ -274,7 +283,9 @@ export function main() {
|
|||
const {view} = createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, null, null, 4, 'div'),
|
||||
directiveDef(NodeFlags.None, null, 1, QueryService, []),
|
||||
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}),
|
||||
queryDef(
|
||||
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
|
||||
{'a': QueryBindingType.First}),
|
||||
aServiceProvider(),
|
||||
aServiceProvider(),
|
||||
]));
|
||||
|
@ -293,9 +304,11 @@ export function main() {
|
|||
}
|
||||
|
||||
const {view} = createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, [['query1', QueryValueType.ElementRef]], null, 2, 'div'),
|
||||
elementDef(NodeFlags.None, [[someQueryId, QueryValueType.ElementRef]], null, 2, 'div'),
|
||||
directiveDef(NodeFlags.None, null, 1, QueryService, []),
|
||||
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}),
|
||||
queryDef(
|
||||
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
|
||||
{'a': QueryBindingType.First}),
|
||||
]));
|
||||
|
||||
Services.checkAndUpdateView(view);
|
||||
|
@ -311,10 +324,12 @@ export function main() {
|
|||
|
||||
const {view} = createAndGetRootNodes(compViewDef([
|
||||
anchorDef(
|
||||
NodeFlags.None, [['query1', QueryValueType.TemplateRef]], null, 2,
|
||||
NodeFlags.None, [[someQueryId, QueryValueType.TemplateRef]], null, 2,
|
||||
embeddedViewDef([anchorDef(NodeFlags.None, null, null, 0)])),
|
||||
directiveDef(NodeFlags.None, null, 1, QueryService, []),
|
||||
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}),
|
||||
queryDef(
|
||||
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
|
||||
{'a': QueryBindingType.First}),
|
||||
]));
|
||||
|
||||
Services.checkAndUpdateView(view);
|
||||
|
@ -329,9 +344,11 @@ export function main() {
|
|||
}
|
||||
|
||||
const {view} = createAndGetRootNodes(compViewDef([
|
||||
anchorDef(NodeFlags.None, [['query1', QueryValueType.ViewContainerRef]], null, 2),
|
||||
anchorDef(NodeFlags.None, [[someQueryId, QueryValueType.ViewContainerRef]], null, 2),
|
||||
directiveDef(NodeFlags.None, null, 1, QueryService, []),
|
||||
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}),
|
||||
queryDef(
|
||||
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
|
||||
{'a': QueryBindingType.First}),
|
||||
]));
|
||||
|
||||
Services.checkAndUpdateView(view);
|
||||
|
@ -367,7 +384,7 @@ export function main() {
|
|||
expect(err).toBeTruthy();
|
||||
expect(err.message)
|
||||
.toBe(
|
||||
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'Query query1 not dirty'. Current value: 'Query query1 dirty'.`);
|
||||
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'Query 1 not dirty'. Current value: 'Query 1 dirty'.`);
|
||||
const debugCtx = getDebugContext(err);
|
||||
expect(debugCtx.view).toBe(view);
|
||||
expect(debugCtx.nodeIndex).toBe(2);
|
||||
|
@ -381,7 +398,9 @@ export function main() {
|
|||
const {view} = createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, null, null, 3, 'div'),
|
||||
directiveDef(NodeFlags.None, null, 1, QueryService, []),
|
||||
queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.All}),
|
||||
queryDef(
|
||||
NodeFlags.HasContentQuery | NodeFlags.HasDynamicQuery, someQueryId,
|
||||
{'a': QueryBindingType.All}),
|
||||
aServiceProvider(),
|
||||
]));
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ export function main() {
|
|||
directiveDef(
|
||||
NodeFlags.None, null, 0, AComp, [], null, null,
|
||||
() => compViewDef([
|
||||
elementDef(NodeFlags.None, [['#ref', QueryValueType.ElementRef]], null, 2, 'span'),
|
||||
elementDef(NodeFlags.None, [['ref', QueryValueType.ElementRef]], null, 2, 'span'),
|
||||
directiveDef(NodeFlags.None, null, 0, AService, []), textDef(null, ['a'])
|
||||
])),
|
||||
]));
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import {NodeFlags, QueryValueType, ViewData, ViewDefinition, ViewFlags, anchorDef, directiveDef, elementDef, textDef, viewDef} from '@angular/core/src/view/index';
|
||||
import {filterQueryId} from '@angular/core/src/view/util';
|
||||
|
||||
export function main() {
|
||||
describe('viewDef', () => {
|
||||
|
@ -76,7 +77,7 @@ export function main() {
|
|||
|
||||
describe('parent', () => {
|
||||
function parents(viewDef: ViewDefinition): number[] {
|
||||
return viewDef.nodes.map(node => node.parent);
|
||||
return viewDef.nodes.map(node => node.parent ? node.parent.index : null);
|
||||
}
|
||||
|
||||
it('should calculate parents for one level', () => {
|
||||
|
@ -86,7 +87,7 @@ export function main() {
|
|||
textDef(null, ['a']),
|
||||
]);
|
||||
|
||||
expect(parents(vd)).toEqual([undefined, 0, 0]);
|
||||
expect(parents(vd)).toEqual([null, 0, 0]);
|
||||
});
|
||||
|
||||
it('should calculate parents for one level, multiple roots', () => {
|
||||
|
@ -98,7 +99,7 @@ export function main() {
|
|||
textDef(null, ['a']),
|
||||
]);
|
||||
|
||||
expect(parents(vd)).toEqual([undefined, 0, undefined, 2, undefined]);
|
||||
expect(parents(vd)).toEqual([null, 0, null, 2, null]);
|
||||
});
|
||||
|
||||
it('should calculate parents for multiple levels', () => {
|
||||
|
@ -111,7 +112,7 @@ export function main() {
|
|||
textDef(null, ['a']),
|
||||
]);
|
||||
|
||||
expect(parents(vd)).toEqual([undefined, 0, 1, undefined, 3, undefined]);
|
||||
expect(parents(vd)).toEqual([null, 0, 1, null, 3, null]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -175,52 +176,56 @@ export function main() {
|
|||
});
|
||||
|
||||
describe('childMatchedQueries', () => {
|
||||
function childMatchedQueries(viewDef: ViewDefinition): string[][] {
|
||||
return viewDef.nodes.map(node => Object.keys(node.childMatchedQueries).sort());
|
||||
function childMatchedQueries(viewDef: ViewDefinition): number[] {
|
||||
return viewDef.nodes.map(node => node.childMatchedQueries);
|
||||
}
|
||||
|
||||
it('should calculate childMatchedQueries for one level', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, null, null, 1, 'span'),
|
||||
directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, [])
|
||||
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, [])
|
||||
]);
|
||||
|
||||
expect(childMatchedQueries(vd)).toEqual([['q1'], []]);
|
||||
expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), 0]);
|
||||
});
|
||||
|
||||
it('should calculate childMatchedQueries for two levels', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, null, null, 2, 'span'),
|
||||
elementDef(NodeFlags.None, null, null, 1, 'span'),
|
||||
directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, [])
|
||||
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, [])
|
||||
]);
|
||||
|
||||
expect(childMatchedQueries(vd)).toEqual([['q1'], ['q1'], []]);
|
||||
expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), filterQueryId(1), 0]);
|
||||
});
|
||||
|
||||
it('should calculate childMatchedQueries for one level, multiple roots', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, null, null, 1, 'span'),
|
||||
directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, []),
|
||||
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, []),
|
||||
elementDef(NodeFlags.None, null, null, 2, 'span'),
|
||||
directiveDef(NodeFlags.None, [['q2', QueryValueType.Provider]], 0, AService, []),
|
||||
directiveDef(NodeFlags.None, [['q3', QueryValueType.Provider]], 0, AService, []),
|
||||
directiveDef(NodeFlags.None, [[2, QueryValueType.Provider]], 0, AService, []),
|
||||
directiveDef(NodeFlags.None, [[3, QueryValueType.Provider]], 0, AService, []),
|
||||
]);
|
||||
|
||||
expect(childMatchedQueries(vd)).toEqual([['q1'], [], ['q2', 'q3'], [], []]);
|
||||
expect(childMatchedQueries(vd)).toEqual([
|
||||
filterQueryId(1), 0, filterQueryId(2) | filterQueryId(3), 0, 0
|
||||
]);
|
||||
});
|
||||
|
||||
it('should calculate childMatchedQueries for multiple levels', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, null, null, 2, 'span'),
|
||||
elementDef(NodeFlags.None, null, null, 1, 'span'),
|
||||
directiveDef(NodeFlags.None, [['q1', QueryValueType.Provider]], 0, AService, []),
|
||||
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, []),
|
||||
elementDef(NodeFlags.None, null, null, 2, 'span'),
|
||||
directiveDef(NodeFlags.None, [['q2', QueryValueType.Provider]], 0, AService, []),
|
||||
directiveDef(NodeFlags.None, [['q3', QueryValueType.Provider]], 0, AService, []),
|
||||
directiveDef(NodeFlags.None, [[2, QueryValueType.Provider]], 0, AService, []),
|
||||
directiveDef(NodeFlags.None, [[3, QueryValueType.Provider]], 0, AService, []),
|
||||
]);
|
||||
|
||||
expect(childMatchedQueries(vd)).toEqual([['q1'], ['q1'], [], ['q2', 'q3'], [], []]);
|
||||
expect(childMatchedQueries(vd)).toEqual([
|
||||
filterQueryId(1), filterQueryId(1), 0, filterQueryId(2) | filterQueryId(3), 0, 0
|
||||
]);
|
||||
});
|
||||
|
||||
it('should included embedded views into childMatchedQueries', () => {
|
||||
|
@ -231,12 +236,12 @@ export function main() {
|
|||
() => viewDef(
|
||||
ViewFlags.None,
|
||||
[
|
||||
elementDef(NodeFlags.None, [['q1', QueryValueType.Provider]], null, 0, 'span'),
|
||||
elementDef(NodeFlags.None, [[1, QueryValueType.Provider]], null, 0, 'span'),
|
||||
]))
|
||||
]);
|
||||
|
||||
// Note: the template will become a sibling to the anchor once stamped out,
|
||||
expect(childMatchedQueries(vd)).toEqual([['q1'], []]);
|
||||
expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), 0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -389,7 +389,11 @@ export class DomRendererV2 implements RendererV2 {
|
|||
}
|
||||
}
|
||||
|
||||
removeChild(parent: any, oldChild: any): void { parent.removeChild(oldChild); }
|
||||
removeChild(parent: any, oldChild: any): void {
|
||||
if (parent) {
|
||||
parent.removeChild(oldChild);
|
||||
}
|
||||
}
|
||||
|
||||
selectRootElement(selectorOrNode: string|any, debugInfo?: any): any {
|
||||
let el: any = typeof selectorOrNode === 'string' ? document.querySelector(selectorOrNode) :
|
||||
|
|
|
@ -259,7 +259,11 @@ export class ServerRendererV2 implements RendererV2 {
|
|||
}
|
||||
}
|
||||
|
||||
removeChild(parent: any, oldChild: any): void { getDOM().removeChild(parent, oldChild); }
|
||||
removeChild(parent: any, oldChild: any): void {
|
||||
if (parent) {
|
||||
getDOM().removeChild(parent, oldChild);
|
||||
}
|
||||
}
|
||||
|
||||
selectRootElement(selectorOrNode: string|any, debugInfo?: any): any {
|
||||
let el: any;
|
||||
|
|
Loading…
Reference in New Issue