fix(compiler): Allow templates to access variables that are declared afterwards.

Fixes #8261
This commit is contained in:
Tobias Bosch 2016-04-27 11:22:44 -07:00
parent c209836fd0
commit 1e8864c4a5
4 changed files with 26 additions and 24 deletions

View File

@ -103,6 +103,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
bindDirectiveDestroyLifecycleCallbacks(directiveAst.directive, directiveInstance, bindDirectiveDestroyLifecycleCallbacks(directiveAst.directive, directiveInstance,
compileElement); compileElement);
}); });
bindView(compileElement.embeddedView, ast.children);
return null; return null;
} }

View File

@ -50,8 +50,6 @@ import {
CompileTokenMetadata CompileTokenMetadata
} from '../compile_metadata'; } from '../compile_metadata';
import {bindView} from './view_binder';
const IMPLICIT_TEMPLATE_VAR = '\$implicit'; const IMPLICIT_TEMPLATE_VAR = '\$implicit';
const CLASS_ATTR = 'class'; const CLASS_ATTR = 'class';
const STYLE_ATTR = 'style'; const STYLE_ATTR = 'style';
@ -65,28 +63,28 @@ export class ViewCompileDependency {
} }
export function buildView(view: CompileView, template: TemplateAst[], export function buildView(view: CompileView, template: TemplateAst[],
targetDependencies: ViewCompileDependency[], targetDependencies: ViewCompileDependency[]): number {
targetStatements: o.Statement[]): number { var builderVisitor = new ViewBuilderVisitor(view, targetDependencies);
var builderVisitor = new ViewBuilderVisitor(view, targetDependencies, targetStatements);
templateVisitAll(builderVisitor, template, view.declarationElement.isNull() ? templateVisitAll(builderVisitor, template, view.declarationElement.isNull() ?
view.declarationElement : view.declarationElement :
view.declarationElement.parent); view.declarationElement.parent);
// Need to separate binding from creation to be able to refer to
// variables that have been declared after usage.
bindView(view, template);
view.afterNodes();
createViewTopLevelStmts(view, targetStatements);
return builderVisitor.nestedViewCount; return builderVisitor.nestedViewCount;
} }
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);
}
});
}
class ViewBuilderVisitor implements TemplateAstVisitor { class ViewBuilderVisitor implements TemplateAstVisitor {
nestedViewCount: number = 0; nestedViewCount: number = 0;
constructor(public view: CompileView, public targetDependencies: ViewCompileDependency[], constructor(public view: CompileView, public targetDependencies: ViewCompileDependency[]) {}
public targetStatements: o.Statement[]) {}
private _isRootNode(parent: CompileElement): boolean { return parent.view !== this.view; } private _isRootNode(parent: CompileElement): boolean { return parent.view !== this.view; }
@ -284,8 +282,7 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
var embeddedView = new CompileView( var embeddedView = new CompileView(
this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR, this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR,
this.view.viewIndex + this.nestedViewCount, compileElement, templateVariableBindings); this.view.viewIndex + this.nestedViewCount, compileElement, templateVariableBindings);
this.nestedViewCount += this.nestedViewCount += buildView(embeddedView, ast.children, this.targetDependencies);
buildView(embeddedView, ast.children, this.targetDependencies, this.targetStatements);
compileElement.beforeChildren(); compileElement.beforeChildren();
this._addRootNodeAndProject(compileElement, ast.ngContentIndex, parent); this._addRootNodeAndProject(compileElement, ast.ngContentIndex, parent);

View File

@ -3,7 +3,8 @@ import {Injectable} from 'angular2/src/core/di';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {buildView, ViewCompileDependency} from './view_builder'; import {buildView, finishView, ViewCompileDependency} from './view_builder';
import {bindView} from './view_binder';
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
@ -25,7 +26,12 @@ export class ViewCompiler {
var dependencies = []; var dependencies = [];
var view = new CompileView(component, this._genConfig, pipes, styles, 0, var view = new CompileView(component, this._genConfig, pipes, styles, 0,
CompileElement.createNull(), []); CompileElement.createNull(), []);
buildView(view, template, dependencies, statements); buildView(view, template, dependencies);
// Need to separate binding from creation to be able to refer to
// variables that have been declared after usage.
bindView(view, template);
finishView(view, statements);
return new ViewCompileResult(statements, view.viewFactory.name, dependencies); return new ViewCompileResult(statements, view.viewFactory.name, dependencies);
} }
} }

View File

@ -587,18 +587,16 @@ function declareTests(isJit: boolean) {
(tcb: TestComponentBuilder, async) => { (tcb: TestComponentBuilder, async) => {
tcb.overrideView( tcb.overrideView(
MyComp, new ViewMetadata({ MyComp, new ViewMetadata({
template: '<p>{{alice.ctxProp}}<child-cmp var-alice></child-cmp></p>', template:
directives: [ChildComp] '<template [ngIf]="true">{{alice.ctxProp}}</template>|{{alice.ctxProp}}|<child-cmp var-alice></child-cmp>',
directives: [ChildComp, NgIf]
})) }))
.createAsync(MyComp) .createAsync(MyComp)
.then((fixture) => { .then((fixture) => {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.nativeElement) expect(fixture.debugElement.nativeElement).toHaveText('hello|hello|hello');
.toHaveText('hellohello'); // this first one is the
// component, the second one is
// the text binding
async.done(); async.done();
})})); })}));