Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(compiler): query <template> elements before their children. #13677

Merged
merged 1 commit into from
Dec 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 8 additions & 40 deletions modules/@angular/compiler/src/view_compiler/compile_element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,7 @@ export class CompileElement extends CompileNode {
const directiveInstance = this.instances.get(tokenReference(identifierToken(directive.type)));
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 => {
const token = this.referenceTokens[varName];
let varValue: o.Expression;
Expand All @@ -226,27 +221,6 @@ export class CompileElement extends CompileNode {
varValue = this.renderNode;
}
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);
}
});
}

Expand All @@ -265,12 +239,14 @@ export class CompileElement extends CompileNode {
this.view.injectorGetMethod.addStmt(createInjectInternalCondition(
this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr));
});
}

finish() {
Array.from(this._queries.values())
.forEach(
queries => queries.forEach(
q =>
q.afterChildren(this.view.createMethod, this.view.updateContentQueriesMethod)));
q => q.generateStatements(
this.view.createMethod, this.view.updateContentQueriesMethod)));
}

addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) {
Expand All @@ -283,12 +259,11 @@ export class CompileElement extends CompileNode {
null;
}

getProviderTokens(): o.Expression[] {
return Array.from(this._resolvedProviders.values())
.map((resolvedProvider) => createDiTokenExpression(resolvedProvider.token));
getProviderTokens(): CompileTokenMetadata[] {
return Array.from(this._resolvedProviders.values()).map(provider => provider.token);
}

private _getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
const result: CompileQuery[] = [];
let currentEl: CompileElement = this;
let distance = 0;
Expand Down Expand Up @@ -426,10 +401,3 @@ function createProviderProperty(
}
return o.THIS_EXPR.prop(propName);
}

class _QueryWithRead {
public read: CompileTokenMetadata;
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
this.read = query.meta.read || match;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class CompileQuery {
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 updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
if (isPresent(this.ownerDirectiveExpression)) {
Expand Down
4 changes: 2 additions & 2 deletions modules/@angular/compiler/src/view_compiler/compile_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,11 @@ export class CompileView implements NameResolver {
}
}

afterNodes() {
finish() {
Array.from(this.viewQueries.values())
.forEach(
queries => queries.forEach(
q => q.afterChildren(this.createMethod, this.updateViewQueriesMethod)));
q => q.generateStatements(this.createMethod, this.updateViewQueriesMethod)));
}
}

Expand Down
55 changes: 55 additions & 0 deletions modules/@angular/compiler/src/view_compiler/query_binder.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
3 changes: 3 additions & 0 deletions modules/@angular/compiler/src/view_compiler/view_binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {CompileView} from './compile_view';
import {bindOutputs} from './event_binder';
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
import {bindQueryValues} from './query_binder';

export function bindView(
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
Expand Down Expand Up @@ -43,6 +44,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {

visitElement(ast: ElementAst, parent: CompileElement): any {
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
bindQueryValues(compileElement);
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement);
ast.directives.forEach((directiveAst, dirIndex) => {
Expand Down Expand Up @@ -75,6 +77,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {

visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
bindQueryValues(compileElement);
bindOutputs(ast.outputs, ast.directives, compileElement, false);
ast.directives.forEach((directiveAst, dirIndex) => {
const directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
Expand Down
15 changes: 10 additions & 5 deletions modules/@angular/compiler/src/view_compiler/view_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,16 @@ export function buildView(
}

export function finishView(view: CompileView, targetStatements: o.Statement[]) {
view.afterNodes();
createViewTopLevelStmts(view, targetStatements);
view.nodes.forEach((node) => {
if (node instanceof CompileElement && node.hasEmbeddedView) {
finishView(node.embeddedView, targetStatements);
if (node instanceof CompileElement) {
node.finish();
if (node.hasEmbeddedView) {
finishView(node.embeddedView, targetStatements);
}
}
});
view.finish();
createViewTopLevelStmts(view, targetStatements);
}

class ViewBuilderVisitor implements TemplateAstVisitor {
Expand Down Expand Up @@ -418,7 +421,9 @@ function createStaticNodeDebugInfo(node: CompileNode): o.Expression {
let componentToken: o.Expression = o.NULL_EXPR;
const varTokenEntries: any[] = [];
if (isPresent(compileElement)) {
providerTokens = compileElement.getProviderTokens();
providerTokens =
compileElement.getProviderTokens().map((token) => createDiTokenExpression(token));

if (isPresent(compileElement.component)) {
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
}
Expand Down
28 changes: 28 additions & 0 deletions modules/@angular/core/test/linker/query_integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export function main() {
NeedsContentChildWithRead,
NeedsViewChildrenWithRead,
NeedsViewChildWithRead,
NeedsContentChildTemplateRef,
NeedsContentChildTemplateRefApp,
NeedsViewContainerWithRead,
ManualProjecting
]
Expand Down Expand Up @@ -262,6 +264,15 @@ export function main() {
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', () => {
const template = '<needs-view-child-read></needs-view-child-read>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
Expand Down Expand Up @@ -730,6 +741,23 @@ class NeedsContentChildWithRead {
@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({
selector: 'needs-view-children-read',
template: '<div #q text="va"></div><div #w text="vb"></div>',
Expand Down