Skip to content

Commit

Permalink
fix(compiler): query <template> elements before their children. (an…
Browse files Browse the repository at this point in the history
  • Loading branch information
tbosch authored and hansl committed Dec 28, 2016
1 parent 07e0fce commit 7c21064
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 48 deletions.
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 @@ -210,12 +210,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 @@ -225,27 +220,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 @@ -264,12 +238,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 @@ -282,12 +258,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 @@ -425,10 +400,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 @@ -416,7 +419,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

0 comments on commit 7c21064

Please sign in to comment.