Skip to content

Commit 3c1a162

Browse files
karaIgorMinar
authored andcommitted
fix(ivy): support static ContentChild queries (angular#28811)
This commit adds support for the `static: true` flag in `ContentChild` queries. Prior to this commit, all `ContentChild` queries were resolved after change detection ran. This is a problem for backwards compatibility because View Engine also supported "static" queries which would resolve before change detection. Now if users add a `static: true` option, the query will be resolved in creation mode (before change detection runs). For example: ```ts @ContentChild(TemplateRef, {static: true}) template !: TemplateRef; ``` This feature will come in handy for components that need to create components dynamically. PR Close angular#28811
1 parent a4638d5 commit 3c1a162

File tree

10 files changed

+269
-58
lines changed

10 files changed

+269
-58
lines changed

packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,6 +1694,79 @@ describe('compiler compliance', () => {
16941694
expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration');
16951695
});
16961696

1697+
it('should support static content queries', () => {
1698+
const files = {
1699+
app: {
1700+
...directive,
1701+
'content_query.ts': `
1702+
import {Component, ContentChild, NgModule} from '@angular/core';
1703+
import {SomeDirective} from './some.directive';
1704+
1705+
@Component({
1706+
selector: 'content-query-component',
1707+
template: \`
1708+
<div><ng-content></ng-content></div>
1709+
\`
1710+
})
1711+
export class ContentQueryComponent {
1712+
@ContentChild(SomeDirective, {static: true}) someDir !: SomeDirective;
1713+
@ContentChild('foo', {static: false}) foo !: ElementRef;
1714+
}
1715+
1716+
@Component({
1717+
selector: 'my-app',
1718+
template: \`
1719+
<content-query-component>
1720+
<div someDir></div>
1721+
</content-query-component>
1722+
\`
1723+
})
1724+
export class MyApp { }
1725+
1726+
@NgModule({declarations: [SomeDirective, ContentQueryComponent, MyApp]})
1727+
export class MyModule { }
1728+
`
1729+
}
1730+
};
1731+
1732+
const ContentQueryComponentDefinition = `
1733+
ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
1734+
type: ContentQueryComponent,
1735+
selectors: [["content-query-component"]],
1736+
factory: function ContentQueryComponent_Factory(t) {
1737+
return new (t || ContentQueryComponent)();
1738+
},
1739+
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
1740+
if (rf & 1) {
1741+
$r3$.ɵstaticContentQuery(dirIndex, SomeDirective, true, null);
1742+
$r3$.ɵcontentQuery(dirIndex, $ref0$, true, null);
1743+
}
1744+
if (rf & 2) {
1745+
var $tmp$;
1746+
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadContentQuery())) && (ctx.someDir = $tmp$.first));
1747+
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadContentQuery())) && (ctx.foo = $tmp$.first));
1748+
}
1749+
},
1750+
ngContentSelectors: $_c1$,
1751+
consts: 2,
1752+
vars: 0,
1753+
template: function ContentQueryComponent_Template(rf, ctx) {
1754+
if (rf & 1) {
1755+
$r3$.ɵprojectionDef();
1756+
$r3$.ɵelementStart(0, "div");
1757+
$r3$.ɵprojection(1);
1758+
$r3$.ɵelementEnd();
1759+
}
1760+
},
1761+
encapsulation: 2
1762+
});`;
1763+
1764+
const result = compile(files, angularFiles);
1765+
const source = result.source;
1766+
1767+
expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration');
1768+
});
1769+
16971770
it('should support content queries with read tokens specified', () => {
16981771
const files = {
16991772
app: {

packages/compiler/src/render3/r3_identifiers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export class Identifiers {
187187
static queryRefresh: o.ExternalReference = {name: 'ɵqueryRefresh', moduleName: CORE};
188188
static viewQuery: o.ExternalReference = {name: 'ɵviewQuery', moduleName: CORE};
189189
static staticViewQuery: o.ExternalReference = {name: 'ɵstaticViewQuery', moduleName: CORE};
190+
static staticContentQuery: o.ExternalReference = {name: 'ɵstaticContentQuery', moduleName: CORE};
190191
static loadViewQuery: o.ExternalReference = {name: 'ɵloadViewQuery', moduleName: CORE};
191192
static contentQuery: o.ExternalReference = {name: 'ɵcontentQuery', moduleName: CORE};
192193
static loadContentQuery: o.ExternalReference = {name: 'ɵloadContentQuery', moduleName: CORE};

packages/compiler/src/render3/view/compiler.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,9 +518,12 @@ function createContentQueriesFunction(
518518
const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME);
519519

520520
for (const query of meta.queries) {
521-
// creation, e.g. r3.contentQuery(dirIndex, somePredicate, true);
521+
// creation, e.g. r3.contentQuery(dirIndex, somePredicate, true, null);
522522
const args = [o.variable('dirIndex'), ...prepareQueryParams(query, constantPool) as any];
523-
createStatements.push(o.importExpr(R3.contentQuery).callFn(args).toStmt());
523+
524+
const queryInstruction = query.static ? R3.staticContentQuery : R3.contentQuery;
525+
526+
createStatements.push(o.importExpr(queryInstruction).callFn(args).toStmt());
524527

525528
// update, e.g. (r3.queryRefresh(tmp = r3.loadContentQuery()) && (ctx.someDir = tmp));
526529
const temporary = tempAllocator();

packages/core/src/core_render3_private_export.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export {
8282
queryRefresh as ɵqueryRefresh,
8383
viewQuery as ɵviewQuery,
8484
staticViewQuery as ɵstaticViewQuery,
85+
staticContentQuery as ɵstaticContentQuery,
8586
loadViewQuery as ɵloadViewQuery,
8687
contentQuery as ɵcontentQuery,
8788
loadContentQuery as ɵloadContentQuery,

packages/core/src/render3/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export {
128128
loadViewQuery,
129129
contentQuery,
130130
loadContentQuery,
131+
staticContentQuery
131132
} from './query';
132133

133134
export {

packages/core/src/render3/instructions.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ const enum BindingDirection {
6565
*/
6666
export function refreshDescendantViews(lView: LView) {
6767
const tView = lView[TVIEW];
68+
const creationMode = isCreationMode(lView);
69+
6870
// This needs to be set before children are processed to support recursive components
6971
tView.firstTemplatePass = false;
7072

@@ -73,7 +75,7 @@ export function refreshDescendantViews(lView: LView) {
7375

7476
// If this is a creation pass, we should not call lifecycle hooks or evaluate bindings.
7577
// This will be done in the update pass.
76-
if (!isCreationMode(lView)) {
78+
if (!creationMode) {
7779
const checkNoChangesMode = getCheckNoChangesMode();
7880

7981
executeInitHooks(lView, tView, checkNoChangesMode);
@@ -90,6 +92,13 @@ export function refreshDescendantViews(lView: LView) {
9092
setHostBindings(tView, lView);
9193
}
9294

95+
// We resolve content queries specifically marked as `static` in creation mode. Dynamic
96+
// content queries are resolved during change detection (i.e. update mode), after embedded
97+
// views are refreshed (see block above).
98+
if (creationMode && tView.staticContentQueries) {
99+
refreshContentQueries(tView, lView);
100+
}
101+
93102
refreshChildComponents(tView.components);
94103
}
95104

@@ -785,6 +794,7 @@ export function createTView(
785794
expandoInstructions: null,
786795
firstTemplatePass: true,
787796
staticViewQueries: false,
797+
staticContentQueries: false,
788798
initHooks: null,
789799
checkHooks: null,
790800
contentHooks: null,

packages/core/src/render3/interfaces/view.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,14 @@ export interface TView {
384384
*/
385385
staticViewQueries: boolean;
386386

387+
/**
388+
* Whether or not there are any static content queries tracked on this view.
389+
*
390+
* We store this so we know whether or not we should do a content query
391+
* refresh after creation mode to collect static query results.
392+
*/
393+
staticContentQueries: boolean;
394+
387395
/**
388396
* The index where the viewQueries section of `LView` begins. This section contains
389397
* view queries defined for a component/directive.

packages/core/src/render3/jit/environment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
8989
'ɵqueryRefresh': r3.queryRefresh,
9090
'ɵviewQuery': r3.viewQuery,
9191
'ɵstaticViewQuery': r3.staticViewQuery,
92+
'ɵstaticContentQuery': r3.staticContentQuery,
9293
'ɵloadViewQuery': r3.loadViewQuery,
9394
'ɵcontentQuery': r3.contentQuery,
9495
'ɵloadContentQuery': r3.loadContentQuery,

packages/core/src/render3/query.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ export function loadViewQuery<T>(): T {
442442
*/
443443
export function contentQuery<T>(
444444
directiveIndex: number, predicate: Type<any>| string[], descend: boolean,
445-
// TODO: "read" should be an AbstractType (FW-486)
445+
// TODO(FW-486): "read" should be an AbstractType
446446
read: any): QueryList<T> {
447447
const lView = getLView();
448448
const tView = lView[TVIEW];
@@ -459,6 +459,28 @@ export function contentQuery<T>(
459459
return contentQuery;
460460
}
461461

462+
/**
463+
* Registers a QueryList, associated with a static content query, for later refresh
464+
* (part of a view refresh).
465+
*
466+
* @param directiveIndex Current directive index
467+
* @param predicate The type for which the query will search
468+
* @param descend Whether or not to descend into children
469+
* @param read What to save in the query
470+
* @returns QueryList<T>
471+
*/
472+
export function staticContentQuery<T>(
473+
directiveIndex: number, predicate: Type<any>| string[], descend: boolean,
474+
// TODO(FW-486): "read" should be an AbstractType
475+
read: any): void {
476+
const queryList = contentQuery(directiveIndex, predicate, descend, read) as QueryList_<T>;
477+
const tView = getLView()[TVIEW];
478+
queryList._static = true;
479+
if (!tView.staticContentQueries) {
480+
tView.staticContentQueries = true;
481+
}
482+
}
483+
462484
export function loadContentQuery<T>(): QueryList<T> {
463485
const lView = getLView();
464486
ngDevMode &&

0 commit comments

Comments
 (0)