From c3d2459a4e981f96891f14ce460435b379564642 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Mon, 6 Jun 2016 08:16:45 -0700 Subject: [PATCH] fix(query): set fixed `@ViewChild` / `@ContentChild` right after the view is created This is needed to have a true replacement of the previous `DynamicComponentLoader.loadNextToLocation`, so that components can be loaded into the view before change detection runs. Closes #9040 --- .../src/view_compiler/compile_element.ts | 3 +- .../src/view_compiler/compile_query.ts | 16 +++++++-- .../src/view_compiler/compile_view.ts | 3 +- .../test/linker/query_integration_spec.ts | 36 +++++++++++++++++++ 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/modules/@angular/compiler/src/view_compiler/compile_element.ts b/modules/@angular/compiler/src/view_compiler/compile_element.ts index a448f968f4..79f48dc2b4 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_element.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_element.ts @@ -230,7 +230,8 @@ export class CompileElement extends CompileNode { this._queries.values().forEach( (queries) => - queries.forEach((query) => query.afterChildren(this.view.updateContentQueriesMethod))); + queries.forEach((query) => query.afterChildren(this.view.createMethod, + this.view.updateContentQueriesMethod))); } addContentNode(ngContentIndex: number, nodeExpr: o.Expression) { diff --git a/modules/@angular/compiler/src/view_compiler/compile_query.ts b/modules/@angular/compiler/src/view_compiler/compile_query.ts index 84fa9fe73d..b55b61a371 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_query.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_query.ts @@ -53,7 +53,11 @@ export class CompileQuery { } } - afterChildren(targetMethod: CompileMethod) { + private _isStatic(): boolean { + return !this._values.values.some(value => value instanceof ViewQueryValues); + } + + afterChildren(targetStaticMethod, targetDynamicMethod: CompileMethod) { var values = createQueryValues(this._values); var updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()]; if (isPresent(this.ownerDirectiveExpression)) { @@ -64,7 +68,15 @@ export class CompileQuery { if (!this.meta.first) { updateStmts.push(this.queryList.callMethod('notifyOnChanges', []).toStmt()); } - targetMethod.addStmt(new o.IfStmt(this.queryList.prop('dirty'), updateStmts)); + if (this.meta.first && this._isStatic()) { + // for queries that don't change and the user asked for a single element, + // set it immediately. That is e.g. needed for querying for ViewContainerRefs, ... + // we don't do this for QueryLists for now as this would break the timing when + // we call QueryList listeners... + targetStaticMethod.addStmts(updateStmts); + } else { + targetDynamicMethod.addStmt(new o.IfStmt(this.queryList.prop('dirty'), updateStmts)); + } } } diff --git a/modules/@angular/compiler/src/view_compiler/compile_view.ts b/modules/@angular/compiler/src/view_compiler/compile_view.ts index 2694e4f6cd..73c159a3aa 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_view.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_view.ts @@ -196,7 +196,8 @@ export class CompileView implements NameResolver { afterNodes() { this.pipes.forEach((pipe) => pipe.create()); this.viewQueries.values().forEach( - (queries) => queries.forEach((query) => query.afterChildren(this.updateViewQueriesMethod))); + (queries) => queries.forEach( + (query) => query.afterChildren(this.createMethod, this.updateViewQueriesMethod))); } } diff --git a/modules/@angular/core/test/linker/query_integration_spec.ts b/modules/@angular/core/test/linker/query_integration_spec.ts index 23df0c048f..e13990707d 100644 --- a/modules/@angular/core/test/linker/query_integration_spec.ts +++ b/modules/@angular/core/test/linker/query_integration_spec.ts @@ -135,6 +135,28 @@ export function main() { }); })); + it('should set static view and content children already after the constructor call', + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + var template = + '
'; + + tcb.overrideTemplate(MyComp0, template) + .createAsync(MyComp0) + .then((view) => { + var q: NeedsStaticContentAndViewChild = + view.debugElement.children[0].references['q']; + expect(q.contentChild.text).toBeFalsy(); + expect(q.viewChild.text).toBeFalsy(); + + view.detectChanges(); + + expect(q.contentChild.text).toEqual('contentFoo'); + expect(q.viewChild.text).toEqual('viewFoo'); + + async.done(); + }); + })); + it('should contain the first view child accross embedded views', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var template = ''; @@ -882,6 +904,19 @@ class NeedsViewChild implements AfterViewInit, } } +@Component({ + selector: 'needs-static-content-view-child', + template: ` +
+ `, + directives: [TextDirective] +}) +class NeedsStaticContentAndViewChild { + @ContentChild(TextDirective) contentChild: TextDirective; + + @ViewChild(TextDirective) viewChild: TextDirective; +} + @Directive({selector: '[dir]'}) @Injectable() class InertDirective { @@ -1119,6 +1154,7 @@ class HasNullQueryCondition { NeedsContentChildren, NeedsViewChildren, NeedsViewChild, + NeedsStaticContentAndViewChild, NeedsContentChild, NeedsTpl, NeedsNamedTpl,