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
This commit is contained in:
Tobias Bosch 2016-06-06 08:16:45 -07:00
parent 8847580fd7
commit c3d2459a4e
4 changed files with 54 additions and 4 deletions

View File

@ -230,7 +230,8 @@ export class CompileElement extends CompileNode {
this._queries.values().forEach( this._queries.values().forEach(
(queries) => (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) { addContentNode(ngContentIndex: number, nodeExpr: o.Expression) {

View File

@ -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 values = createQueryValues(this._values);
var updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()]; var updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
if (isPresent(this.ownerDirectiveExpression)) { if (isPresent(this.ownerDirectiveExpression)) {
@ -64,7 +68,15 @@ export class CompileQuery {
if (!this.meta.first) { if (!this.meta.first) {
updateStmts.push(this.queryList.callMethod('notifyOnChanges', []).toStmt()); 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));
}
} }
} }

View File

@ -196,7 +196,8 @@ export class CompileView implements NameResolver {
afterNodes() { afterNodes() {
this.pipes.forEach((pipe) => pipe.create()); this.pipes.forEach((pipe) => pipe.create());
this.viewQueries.values().forEach( this.viewQueries.values().forEach(
(queries) => queries.forEach((query) => query.afterChildren(this.updateViewQueriesMethod))); (queries) => queries.forEach(
(query) => query.afterChildren(this.createMethod, this.updateViewQueriesMethod)));
} }
} }

View File

@ -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 =
'<needs-static-content-view-child #q><div text="contentFoo"></div></needs-static-content-view-child>';
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', it('should contain the first view child accross embedded views',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<needs-view-child #q></needs-view-child>'; var template = '<needs-view-child #q></needs-view-child>';
@ -882,6 +904,19 @@ class NeedsViewChild implements AfterViewInit,
} }
} }
@Component({
selector: 'needs-static-content-view-child',
template: `
<div text="viewFoo"></div>
`,
directives: [TextDirective]
})
class NeedsStaticContentAndViewChild {
@ContentChild(TextDirective) contentChild: TextDirective;
@ViewChild(TextDirective) viewChild: TextDirective;
}
@Directive({selector: '[dir]'}) @Directive({selector: '[dir]'})
@Injectable() @Injectable()
class InertDirective { class InertDirective {
@ -1119,6 +1154,7 @@ class HasNullQueryCondition {
NeedsContentChildren, NeedsContentChildren,
NeedsViewChildren, NeedsViewChildren,
NeedsViewChild, NeedsViewChild,
NeedsStaticContentAndViewChild,
NeedsContentChild, NeedsContentChild,
NeedsTpl, NeedsTpl,
NeedsNamedTpl, NeedsNamedTpl,