Skip to content

Commit ac87a84

Browse files
committed
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.
1 parent 1280b19 commit ac87a84

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

modules/angular2/src/compiler/view_compiler/compile_element.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,8 @@ export class CompileElement extends CompileNode {
230230

231231
this._queries.values().forEach(
232232
(queries) =>
233-
queries.forEach((query) => query.afterChildren(this.view.updateContentQueriesMethod)));
233+
queries.forEach((query) => query.afterChildren(this.view.createMethod,
234+
this.view.updateContentQueriesMethod)));
234235
}
235236

236237
addContentNode(ngContentIndex: number, nodeExpr: o.Expression) {

modules/angular2/src/compiler/view_compiler/compile_query.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,18 @@ export class CompileQuery {
5959
}
6060
}
6161

62-
afterChildren(targetMethod: CompileMethod) {
62+
private _isStatic(): boolean {
63+
var isStatic = true;
64+
this._values.values.forEach((value) => {
65+
if (value instanceof ViewQueryValues) {
66+
// querying a nested view makes the query content dynamic
67+
isStatic = false;
68+
}
69+
});
70+
return isStatic;
71+
}
72+
73+
afterChildren(targetStaticMethod, targetDynamicMethod: CompileMethod) {
6374
var values = createQueryValues(this._values);
6475
var updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
6576
if (isPresent(this.ownerDirectiveExpression)) {
@@ -70,7 +81,15 @@ export class CompileQuery {
7081
if (!this.meta.first) {
7182
updateStmts.push(this.queryList.callMethod('notifyOnChanges', []).toStmt());
7283
}
73-
targetMethod.addStmt(new o.IfStmt(this.queryList.prop('dirty'), updateStmts));
84+
if (this.meta.first && this._isStatic()) {
85+
// for queries that don't change and the user asked for a single element,
86+
// set it immediately. That is e.g. needed for querying for ViewContainerRefs, ...
87+
// we don't do this for QueryLists for now as this would break the timing when
88+
// we call QueryList listeners...
89+
targetStaticMethod.addStmts(updateStmts);
90+
} else {
91+
targetDynamicMethod.addStmt(new o.IfStmt(this.queryList.prop('dirty'), updateStmts));
92+
}
7493
}
7594
}
7695

modules/angular2/src/compiler/view_compiler/compile_view.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ export class CompileView implements NameResolver {
189189

190190
afterNodes() {
191191
this.viewQueries.values().forEach(
192-
(queries) => queries.forEach((query) => query.afterChildren(this.updateViewQueriesMethod)));
192+
(queries) => queries.forEach(
193+
(query) => query.afterChildren(this.createMethod, this.updateViewQueriesMethod)));
193194
}
194195
}
195196

modules/angular2/test/core/linker/query_integration_spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,28 @@ export function main() {
136136
});
137137
}));
138138

139+
it('should set static view and content children already after the constructor call',
140+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
141+
var template =
142+
'<needs-static-content-view-child #q><div text="contentFoo"></div></needs-static-content-view-child>';
143+
144+
tcb.overrideTemplate(MyComp, template)
145+
.createAsync(MyComp)
146+
.then((view) => {
147+
var q: NeedsStaticContentAndViewChild =
148+
view.debugElement.children[0].getLocal('q');
149+
expect(q.contentChild.text).toBeFalsy();
150+
expect(q.viewChild.text).toBeFalsy();
151+
152+
view.detectChanges();
153+
154+
expect(q.contentChild.text).toEqual('contentFoo');
155+
expect(q.viewChild.text).toEqual('viewFoo');
156+
157+
async.done();
158+
});
159+
}));
160+
139161
it('should contain the first view child accross embedded views',
140162
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
141163
var template = '<needs-view-child #q></needs-view-child>';
@@ -850,6 +872,19 @@ class NeedsViewChild implements AfterViewInit,
850872
ngAfterViewChecked() { this.log.push(["check", isPresent(this.child) ? this.child.text : null]); }
851873
}
852874

875+
@Component({
876+
selector: 'needs-static-content-view-child',
877+
template: `
878+
<div text="viewFoo"></div>
879+
`,
880+
directives: [TextDirective]
881+
})
882+
class NeedsStaticContentAndViewChild {
883+
@ContentChild(TextDirective) contentChild: TextDirective;
884+
885+
@ViewChild(TextDirective) viewChild: TextDirective;
886+
}
887+
853888
@Directive({selector: '[dir]'})
854889
@Injectable()
855890
class InertDirective {
@@ -1074,6 +1109,7 @@ class NeedsViewContainerWithRead {
10741109
NeedsContentChildren,
10751110
NeedsViewChildren,
10761111
NeedsViewChild,
1112+
NeedsStaticContentAndViewChild,
10771113
NeedsContentChild,
10781114
NeedsTpl,
10791115
TextDirective,

0 commit comments

Comments
 (0)