Skip to content

Commit

Permalink
fix(ivy): support static ContentChild queries
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
kara committed Feb 19, 2019
1 parent 8579ca6 commit 681bcae
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,79 @@ describe('compiler compliance', () => {
expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration');
});

it('should support static content queries', () => {
const files = {
app: {
...directive,
'content_query.ts': `
import {Component, ContentChild, NgModule} from '@angular/core';
import {SomeDirective} from './some.directive';
@Component({
selector: 'content-query-component',
template: \`
<div><ng-content></ng-content></div>
\`
})
export class ContentQueryComponent {
@ContentChild(SomeDirective, {static: true}) someDir !: SomeDirective;
@ContentChild('foo', {static: false}) foo !: ElementRef;
}
@Component({
selector: 'my-app',
template: \`
<content-query-component>
<div someDir></div>
</content-query-component>
\`
})
export class MyApp { }
@NgModule({declarations: [SomeDirective, ContentQueryComponent, MyApp]})
export class MyModule { }
`
}
};

const ContentQueryComponentDefinition = `
ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
type: ContentQueryComponent,
selectors: [["content-query-component"]],
factory: function ContentQueryComponent_Factory(t) {
return new (t || ContentQueryComponent)();
},
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵstaticContentQuery(dirIndex, SomeDirective, true, null);
$r3$.ɵcontentQuery(dirIndex, $ref0$, true, null);
}
if (rf & 2) {
var $tmp$;
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadContentQuery())) && (ctx.someDir = $tmp$.first));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadContentQuery())) && (ctx.foo = $tmp$.first));
}
},
ngContentSelectors: $_c1$,
consts: 2,
vars: 0,
template: function ContentQueryComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵprojectionDef();
$r3$.ɵelementStart(0, "div");
$r3$.ɵprojection(1);
$r3$.ɵelementEnd();
}
},
encapsulation: 2
});`;

const result = compile(files, angularFiles);
const source = result.source;

expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration');
});

it('should support content queries with read tokens specified', () => {
const files = {
app: {
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/render3/r3_identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export class Identifiers {
static queryRefresh: o.ExternalReference = {name: 'ɵqueryRefresh', moduleName: CORE};
static viewQuery: o.ExternalReference = {name: 'ɵviewQuery', moduleName: CORE};
static staticViewQuery: o.ExternalReference = {name: 'ɵstaticViewQuery', moduleName: CORE};
static staticContentQuery: o.ExternalReference = {name: 'ɵstaticContentQuery', moduleName: CORE};
static loadViewQuery: o.ExternalReference = {name: 'ɵloadViewQuery', moduleName: CORE};
static contentQuery: o.ExternalReference = {name: 'ɵcontentQuery', moduleName: CORE};
static loadContentQuery: o.ExternalReference = {name: 'ɵloadContentQuery', moduleName: CORE};
Expand Down
7 changes: 5 additions & 2 deletions packages/compiler/src/render3/view/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,9 +517,12 @@ function createContentQueriesFunction(
const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME);

for (const query of meta.queries) {
// creation, e.g. r3.contentQuery(dirIndex, somePredicate, true);
// creation, e.g. r3.contentQuery(dirIndex, somePredicate, true, null);
const args = [o.variable('dirIndex'), ...prepareQueryParams(query, constantPool) as any];
createStatements.push(o.importExpr(R3.contentQuery).callFn(args).toStmt());

const queryInstruction = query.static ? R3.staticContentQuery : R3.contentQuery;

createStatements.push(o.importExpr(queryInstruction).callFn(args).toStmt());

// update, e.g. (r3.queryRefresh(tmp = r3.loadContentQuery()) && (ctx.someDir = tmp));
const temporary = tempAllocator();
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/core_render3_private_export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export {
queryRefresh as ɵqueryRefresh,
viewQuery as ɵviewQuery,
staticViewQuery as ɵstaticViewQuery,
staticContentQuery as ɵstaticContentQuery,
loadViewQuery as ɵloadViewQuery,
contentQuery as ɵcontentQuery,
loadContentQuery as ɵloadContentQuery,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/render3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export {
loadViewQuery,
contentQuery,
loadContentQuery,
staticContentQuery
} from './query';

export {
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/render3/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ const enum BindingDirection {
*/
export function refreshDescendantViews(lView: LView) {
const tView = lView[TVIEW];
const creationMode = isCreationMode(lView);

// This needs to be set before children are processed to support recursive components
tView.firstTemplatePass = false;

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

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

executeInitHooks(lView, tView, checkNoChangesMode);
Expand All @@ -90,6 +92,10 @@ export function refreshDescendantViews(lView: LView) {
setHostBindings(tView, lView);
}

if (creationMode && tView.staticContentQueries) {
refreshContentQueries(tView, lView);
}

refreshChildComponents(tView.components);
}

Expand Down Expand Up @@ -785,6 +791,7 @@ export function createTView(
expandoInstructions: null,
firstTemplatePass: true,
staticViewQueries: false,
staticContentQueries: false,
initHooks: null,
checkHooks: null,
contentHooks: null,
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/render3/interfaces/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,14 @@ export interface TView {
*/
staticViewQueries: boolean;

/**
* Whether or not there are any static content queries tracked on this view.
*
* We store this so we know whether or not we should do a content query
* refresh after creation mode to collect static query results.
*/
staticContentQueries: boolean;

/**
* The index where the viewQueries section of `LView` begins. This section contains
* view queries defined for a component/directive.
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/render3/jit/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵqueryRefresh': r3.queryRefresh,
'ɵviewQuery': r3.viewQuery,
'ɵstaticViewQuery': r3.staticViewQuery,
'ɵstaticContentQuery': r3.staticContentQuery,
'ɵloadViewQuery': r3.loadViewQuery,
'ɵcontentQuery': r3.contentQuery,
'ɵloadContentQuery': r3.loadContentQuery,
Expand Down
22 changes: 22 additions & 0 deletions packages/core/src/render3/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,28 @@ export function contentQuery<T>(
return contentQuery;
}

/**
* Registers a QueryList, associated with a static content query, for later refresh
* (part of a view refresh).
*
* @param directiveIndex Current directive index
* @param predicate The type for which the query will search
* @param descend Whether or not to descend into children
* @param read What to save in the query
* @returns QueryList<T>
*/
export function staticContentQuery<T>(
directiveIndex: number, predicate: Type<any>| string[], descend: boolean,
// TODO: "read" should be an AbstractType (FW-486)
read: any): void {
const queryList = contentQuery(directiveIndex, predicate, descend, read);
const tView = getLView()[TVIEW];
(queryList as QueryList_<T>)._static = true;
if (!tView.staticContentQueries) {
tView.staticContentQueries = true;
}
}

export function loadContentQuery<T>(): QueryList<T> {
const lView = getLView();
ngDevMode &&
Expand Down

0 comments on commit 681bcae

Please sign in to comment.