fix(compiler): query `<template>` elements before their children. (#13677)
Fixes #13118 Closes #13167
This commit is contained in:
parent
07e0fce8fc
commit
7c210645a3
|
@ -210,12 +210,7 @@ export class CompileElement extends CompileNode {
|
||||||
const directiveInstance = this.instances.get(tokenReference(identifierToken(directive.type)));
|
const directiveInstance = this.instances.get(tokenReference(identifierToken(directive.type)));
|
||||||
directive.queries.forEach((queryMeta) => { this._addQuery(queryMeta, directiveInstance); });
|
directive.queries.forEach((queryMeta) => { this._addQuery(queryMeta, directiveInstance); });
|
||||||
}
|
}
|
||||||
const queriesWithReads: _QueryWithRead[] = [];
|
|
||||||
Array.from(this._resolvedProviders.values()).forEach((resolvedProvider) => {
|
|
||||||
const queriesForProvider = this._getQueriesFor(resolvedProvider.token);
|
|
||||||
queriesWithReads.push(
|
|
||||||
...queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
|
|
||||||
});
|
|
||||||
Object.keys(this.referenceTokens).forEach(varName => {
|
Object.keys(this.referenceTokens).forEach(varName => {
|
||||||
const token = this.referenceTokens[varName];
|
const token = this.referenceTokens[varName];
|
||||||
let varValue: o.Expression;
|
let varValue: o.Expression;
|
||||||
|
@ -225,27 +220,6 @@ export class CompileElement extends CompileNode {
|
||||||
varValue = this.renderNode;
|
varValue = this.renderNode;
|
||||||
}
|
}
|
||||||
this.view.locals.set(varName, varValue);
|
this.view.locals.set(varName, varValue);
|
||||||
const varToken = {value: varName};
|
|
||||||
queriesWithReads.push(
|
|
||||||
...this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
|
|
||||||
});
|
|
||||||
queriesWithReads.forEach((queryWithRead) => {
|
|
||||||
let value: o.Expression;
|
|
||||||
if (isPresent(queryWithRead.read.identifier)) {
|
|
||||||
// query for an identifier
|
|
||||||
value = this.instances.get(tokenReference(queryWithRead.read));
|
|
||||||
} else {
|
|
||||||
// query for a reference
|
|
||||||
const token = this.referenceTokens[queryWithRead.read.value];
|
|
||||||
if (isPresent(token)) {
|
|
||||||
value = this.instances.get(tokenReference(token));
|
|
||||||
} else {
|
|
||||||
value = this.elementRef;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isPresent(value)) {
|
|
||||||
queryWithRead.query.addValue(value, this.view);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,12 +238,14 @@ export class CompileElement extends CompileNode {
|
||||||
this.view.injectorGetMethod.addStmt(createInjectInternalCondition(
|
this.view.injectorGetMethod.addStmt(createInjectInternalCondition(
|
||||||
this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr));
|
this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
Array.from(this._queries.values())
|
Array.from(this._queries.values())
|
||||||
.forEach(
|
.forEach(
|
||||||
queries => queries.forEach(
|
queries => queries.forEach(
|
||||||
q =>
|
q => q.generateStatements(
|
||||||
q.afterChildren(this.view.createMethod, this.view.updateContentQueriesMethod)));
|
this.view.createMethod, this.view.updateContentQueriesMethod)));
|
||||||
}
|
}
|
||||||
|
|
||||||
addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) {
|
addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) {
|
||||||
|
@ -282,12 +258,11 @@ export class CompileElement extends CompileNode {
|
||||||
null;
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviderTokens(): o.Expression[] {
|
getProviderTokens(): CompileTokenMetadata[] {
|
||||||
return Array.from(this._resolvedProviders.values())
|
return Array.from(this._resolvedProviders.values()).map(provider => provider.token);
|
||||||
.map((resolvedProvider) => createDiTokenExpression(resolvedProvider.token));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
|
getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
|
||||||
const result: CompileQuery[] = [];
|
const result: CompileQuery[] = [];
|
||||||
let currentEl: CompileElement = this;
|
let currentEl: CompileElement = this;
|
||||||
let distance = 0;
|
let distance = 0;
|
||||||
|
@ -425,10 +400,3 @@ function createProviderProperty(
|
||||||
}
|
}
|
||||||
return o.THIS_EXPR.prop(propName);
|
return o.THIS_EXPR.prop(propName);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _QueryWithRead {
|
|
||||||
public read: CompileTokenMetadata;
|
|
||||||
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
|
|
||||||
this.read = query.meta.read || match;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ export class CompileQuery {
|
||||||
return !this._values.values.some(value => value instanceof ViewQueryValues);
|
return !this._values.values.some(value => value instanceof ViewQueryValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
afterChildren(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
|
generateStatements(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
|
||||||
const values = createQueryValues(this._values);
|
const values = createQueryValues(this._values);
|
||||||
const updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
|
const updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
|
||||||
if (isPresent(this.ownerDirectiveExpression)) {
|
if (isPresent(this.ownerDirectiveExpression)) {
|
||||||
|
|
|
@ -154,11 +154,11 @@ export class CompileView implements NameResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
afterNodes() {
|
finish() {
|
||||||
Array.from(this.viewQueries.values())
|
Array.from(this.viewQueries.values())
|
||||||
.forEach(
|
.forEach(
|
||||||
queries => queries.forEach(
|
queries => queries.forEach(
|
||||||
q => q.afterChildren(this.createMethod, this.updateViewQueriesMethod)));
|
q => q.generateStatements(this.createMethod, this.updateViewQueriesMethod)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {CompileQueryMetadata, CompileTokenMetadata, tokenReference} from '../compile_metadata';
|
||||||
|
import * as o from '../output/output_ast';
|
||||||
|
|
||||||
|
import {CompileElement} from './compile_element';
|
||||||
|
import {CompileQuery} from './compile_query';
|
||||||
|
|
||||||
|
|
||||||
|
// Note: We can't do this when we create the CompileElements already,
|
||||||
|
// as we create embedded views before the <template> elements themselves.
|
||||||
|
export function bindQueryValues(ce: CompileElement) {
|
||||||
|
const queriesWithReads: _QueryWithRead[] = [];
|
||||||
|
ce.getProviderTokens().forEach((token) => {
|
||||||
|
const queriesForProvider = ce.getQueriesFor(token);
|
||||||
|
queriesWithReads.push(...queriesForProvider.map(query => new _QueryWithRead(query, token)));
|
||||||
|
});
|
||||||
|
Object.keys(ce.referenceTokens).forEach(varName => {
|
||||||
|
const token = ce.referenceTokens[varName];
|
||||||
|
const varToken = {value: varName};
|
||||||
|
queriesWithReads.push(
|
||||||
|
...ce.getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
|
||||||
|
});
|
||||||
|
queriesWithReads.forEach((queryWithRead) => {
|
||||||
|
let value: o.Expression;
|
||||||
|
if (queryWithRead.read.identifier) {
|
||||||
|
// query for an identifier
|
||||||
|
value = ce.instances.get(tokenReference(queryWithRead.read));
|
||||||
|
} else {
|
||||||
|
// query for a reference
|
||||||
|
const token = ce.referenceTokens[queryWithRead.read.value];
|
||||||
|
if (token) {
|
||||||
|
value = ce.instances.get(tokenReference(token));
|
||||||
|
} else {
|
||||||
|
value = ce.elementRef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value) {
|
||||||
|
queryWithRead.query.addValue(value, ce.view);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _QueryWithRead {
|
||||||
|
public read: CompileTokenMetadata;
|
||||||
|
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
|
||||||
|
this.read = query.meta.read || match;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ import {CompileView} from './compile_view';
|
||||||
import {bindOutputs} from './event_binder';
|
import {bindOutputs} from './event_binder';
|
||||||
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
||||||
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
|
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
|
||||||
|
import {bindQueryValues} from './query_binder';
|
||||||
|
|
||||||
export function bindView(
|
export function bindView(
|
||||||
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
|
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
|
||||||
|
@ -43,6 +44,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
||||||
|
|
||||||
visitElement(ast: ElementAst, parent: CompileElement): any {
|
visitElement(ast: ElementAst, parent: CompileElement): any {
|
||||||
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||||
|
bindQueryValues(compileElement);
|
||||||
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
|
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
|
||||||
bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement);
|
bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement);
|
||||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||||
|
@ -75,6 +77,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
||||||
|
|
||||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
|
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
|
||||||
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||||
|
bindQueryValues(compileElement);
|
||||||
bindOutputs(ast.outputs, ast.directives, compileElement, false);
|
bindOutputs(ast.outputs, ast.directives, compileElement, false);
|
||||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||||
const directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
|
const directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
|
||||||
|
|
|
@ -48,13 +48,16 @@ export function buildView(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function finishView(view: CompileView, targetStatements: o.Statement[]) {
|
export function finishView(view: CompileView, targetStatements: o.Statement[]) {
|
||||||
view.afterNodes();
|
|
||||||
createViewTopLevelStmts(view, targetStatements);
|
|
||||||
view.nodes.forEach((node) => {
|
view.nodes.forEach((node) => {
|
||||||
if (node instanceof CompileElement && node.hasEmbeddedView) {
|
if (node instanceof CompileElement) {
|
||||||
|
node.finish();
|
||||||
|
if (node.hasEmbeddedView) {
|
||||||
finishView(node.embeddedView, targetStatements);
|
finishView(node.embeddedView, targetStatements);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
view.finish();
|
||||||
|
createViewTopLevelStmts(view, targetStatements);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewBuilderVisitor implements TemplateAstVisitor {
|
class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||||
|
@ -416,7 +419,9 @@ function createStaticNodeDebugInfo(node: CompileNode): o.Expression {
|
||||||
let componentToken: o.Expression = o.NULL_EXPR;
|
let componentToken: o.Expression = o.NULL_EXPR;
|
||||||
const varTokenEntries: any[] = [];
|
const varTokenEntries: any[] = [];
|
||||||
if (isPresent(compileElement)) {
|
if (isPresent(compileElement)) {
|
||||||
providerTokens = compileElement.getProviderTokens();
|
providerTokens =
|
||||||
|
compileElement.getProviderTokens().map((token) => createDiTokenExpression(token));
|
||||||
|
|
||||||
if (isPresent(compileElement.component)) {
|
if (isPresent(compileElement.component)) {
|
||||||
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
|
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,8 @@ export function main() {
|
||||||
NeedsContentChildWithRead,
|
NeedsContentChildWithRead,
|
||||||
NeedsViewChildrenWithRead,
|
NeedsViewChildrenWithRead,
|
||||||
NeedsViewChildWithRead,
|
NeedsViewChildWithRead,
|
||||||
|
NeedsContentChildTemplateRef,
|
||||||
|
NeedsContentChildTemplateRefApp,
|
||||||
NeedsViewContainerWithRead,
|
NeedsViewContainerWithRead,
|
||||||
ManualProjecting
|
ManualProjecting
|
||||||
]
|
]
|
||||||
|
@ -262,6 +264,15 @@ export function main() {
|
||||||
expect(comp.textDirChild.text).toEqual('ca');
|
expect(comp.textDirChild.text).toEqual('ca');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
view.detectChanges();
|
||||||
|
expect(view.nativeElement).toHaveText('OUTER');
|
||||||
|
});
|
||||||
|
|
||||||
it('should contain the first view child', () => {
|
it('should contain the first view child', () => {
|
||||||
const template = '<needs-view-child-read></needs-view-child-read>';
|
const template = '<needs-view-child-read></needs-view-child-read>';
|
||||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||||
|
@ -730,6 +741,23 @@ class NeedsContentChildWithRead {
|
||||||
@ContentChild('nonExisting', {read: TextDirective}) nonExistingVar: TextDirective;
|
@ContentChild('nonExisting', {read: TextDirective}) nonExistingVar: TextDirective;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'needs-content-child-template-ref',
|
||||||
|
template: '<div [ngTemplateOutlet]="templateRef"></div>'
|
||||||
|
})
|
||||||
|
class NeedsContentChildTemplateRef {
|
||||||
|
@ContentChild(TemplateRef) templateRef: TemplateRef<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'needs-content-child-template-ref-app',
|
||||||
|
template: '<needs-content-child-template-ref>' +
|
||||||
|
'<template>OUTER<template>INNER</template></template>' +
|
||||||
|
'</needs-content-child-template-ref>'
|
||||||
|
})
|
||||||
|
class NeedsContentChildTemplateRefApp {
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'needs-view-children-read',
|
selector: 'needs-view-children-read',
|
||||||
template: '<div #q text="va"></div><div #w text="vb"></div>',
|
template: '<div #q text="va"></div><div #w text="vb"></div>',
|
||||||
|
|
Loading…
Reference in New Issue