Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion integration/_payload-limits.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime": 1440,
"main": 12885,
"main": 13019,
"polyfills": 38390
}
}
Expand Down
20 changes: 19 additions & 1 deletion packages/compiler-cli/src/ngtsc/annotations/src/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ export function extractQueryMetadata(
const node = unwrapForwardRef(args[0], reflector);
const arg = evaluator.evaluate(node);

/** Whether or not this query should collect only static results (see view/api.ts) */
let isStatic: boolean = false;
Comment thread
kara marked this conversation as resolved.
Resolved

// Extract the predicate
let predicate: Expression|string[]|null = null;
if (arg instanceof Reference) {
Expand Down Expand Up @@ -263,13 +266,28 @@ export function extractQueryMetadata(
}
descendants = descendantsValue;
}

if (options.has('static')) {
const staticValue = evaluator.evaluate(options.get('static') !);
if (typeof staticValue !== 'boolean') {
throw new FatalDiagnosticError(
ErrorCode.VALUE_HAS_WRONG_TYPE, node, `@${name} options.static must be a boolean`);
}
isStatic = staticValue;
}

} else if (args.length > 2) {
// Too many arguments.
throw new Error(`@${name} has too many arguments`);
}

return {
propertyName, predicate, first, descendants, read,
propertyName,
predicate,
first,
descendants,
read,
static: isStatic,
};
}

Expand Down
150 changes: 142 additions & 8 deletions packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1376,8 +1376,8 @@ describe('compiler compliance', () => {
factory: function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); },
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵviewQuery(SomeDirective, true);
$r3$.ɵviewQuery(SomeDirective, true);
$r3$.ɵviewQuery(SomeDirective, true, null);
$r3$.ɵviewQuery(SomeDirective, true, null);
}
if (rf & 2) {
var $tmp$;
Expand Down Expand Up @@ -1434,8 +1434,8 @@ describe('compiler compliance', () => {
…
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵviewQuery($e0_attrs$, true);
$r3$.ɵviewQuery($e1_attrs$, true);
$r3$.ɵviewQuery($e0_attrs$, true, null);
$r3$.ɵviewQuery($e1_attrs$, true, null);
}
if (rf & 2) {
var $tmp$;
Expand All @@ -1452,6 +1452,67 @@ describe('compiler compliance', () => {
expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration');
});

it('should support static view queries', () => {
const files = {
app: {
...directive,
'view_query.component.ts': `
import {Component, NgModule, ViewChild} from '@angular/core';
import {SomeDirective} from './some.directive';

@Component({
selector: 'view-query-component',
template: \`
<div someDir></div>
\`
})
export class ViewQueryComponent {
@ViewChild(SomeDirective, {static: true}) someDir !: SomeDirective;
@ViewChild('foo', {static: false}) foo !: ElementRef;
}

@NgModule({declarations: [SomeDirective, ViewQueryComponent]})
export class MyModule {}
`
}
};

const ViewQueryComponentDefinition = `
const $refs$ = ["foo"];
const $e0_attrs$ = ["someDir",""];
…
ViewQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
type: ViewQueryComponent,
selectors: [["view-query-component"]],
factory: function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); },
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵstaticViewQuery(SomeDirective, true, null);
$r3$.ɵviewQuery($refs$, true, null);
}
if (rf & 2) {
var $tmp$;
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadViewQuery())) && (ctx.someDir = $tmp$.first));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadViewQuery())) && (ctx.foo = $tmp$.first));
}
},
consts: 1,
vars: 0,
template: function ViewQueryComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵelement(0, "div", $e0_attrs$);
}
},
directives: function () { return [SomeDirective]; },
encapsulation: 2
});`;

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

expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration');
});

it('should support view queries with read tokens specified', () => {
const files = {
app: {
Expand Down Expand Up @@ -1555,8 +1616,8 @@ describe('compiler compliance', () => {
},
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵcontentQuery(dirIndex, SomeDirective, true);
$r3$.ɵcontentQuery(dirIndex, SomeDirective, false);
$r3$.ɵcontentQuery(dirIndex, SomeDirective, true, null);
$r3$.ɵcontentQuery(dirIndex, SomeDirective, false, null);
}
if (rf & 2) {
var $tmp$;
Expand Down Expand Up @@ -1615,8 +1676,8 @@ describe('compiler compliance', () => {
…
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵcontentQuery(dirIndex, $e0_attrs$, true);
$r3$.ɵcontentQuery(dirIndex, $e1_attrs$, false);
$r3$.ɵcontentQuery(dirIndex, $e0_attrs$, true, null);
$r3$.ɵcontentQuery(dirIndex, $e1_attrs$, false, null);
}
if (rf & 2) {
var $tmp$;
Expand All @@ -1633,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
14 changes: 7 additions & 7 deletions packages/compiler-cli/test/ngtsc/ngtsc_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ const trim = (input: string): string => input.replace(/\s+/g, ' ').trim();
const varRegExp = (name: string): RegExp => new RegExp(`var \\w+ = \\[\"${name}\"\\];`);

const viewQueryRegExp = (descend: boolean, ref?: string): RegExp => {
const maybeRef = ref ? `, ${ref}` : ``;
return new RegExp(`i0\\.ɵviewQuery\\(\\w+, ${descend}${maybeRef}\\)`);
const maybeRef = ref ? `${ref}` : `null`;
return new RegExp(`i0\\.ɵviewQuery\\(\\w+, ${descend}, ${maybeRef}\\)`);
};

const contentQueryRegExp = (predicate: string, descend: boolean, ref?: string): RegExp => {
const maybeRef = ref ? `, ${ref}` : ``;
return new RegExp(`i0\\.ɵcontentQuery\\(dirIndex, ${predicate}, ${descend}${maybeRef}\\)`);
const maybeRef = ref ? `${ref}` : `null`;
return new RegExp(`i0\\.ɵcontentQuery\\(dirIndex, ${predicate}, ${descend}, ${maybeRef}\\)`);
};

describe('ngtsc behavioral tests', () => {
Expand Down Expand Up @@ -1017,7 +1017,7 @@ describe('ngtsc behavioral tests', () => {
expect(jsContents).toMatch(varRegExp('accessor'));
// match `i0.ɵcontentQuery(dirIndex, _c1, true, TemplateRef)`
expect(jsContents).toMatch(contentQueryRegExp('\\w+', true, 'TemplateRef'));
// match `i0.ɵviewQuery(_c2, true)`
// match `i0.ɵviewQuery(_c2, true, null)`
expect(jsContents).toMatch(viewQueryRegExp(true));
});

Expand All @@ -1039,9 +1039,9 @@ describe('ngtsc behavioral tests', () => {

env.driveMain();
const jsContents = env.getContents('test.js');
// match `i0.ɵcontentQuery(dirIndex, TemplateRef, true)`
// match `i0.ɵcontentQuery(dirIndex, TemplateRef, true, null)`
expect(jsContents).toMatch(contentQueryRegExp('TemplateRef', true));
// match `i0.ɵcontentQuery(dirIndex, ViewContainerRef, true)`
// match `i0.ɵcontentQuery(dirIndex, ViewContainerRef, true, null)`
expect(jsContents).toMatch(contentQueryRegExp('ViewContainerRef', true));
});

Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/compiler_facade_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export interface R3QueryMetadataFacade {
predicate: any|string[];
descendants: boolean;
read: any|null;
static: boolean;
}

export interface ParseSourceSpan {
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/jit_compiler_facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ function convertToR3QueryMetadata(facade: R3QueryMetadataFacade): R3QueryMetadat
predicate: Array.isArray(facade.predicate) ? facade.predicate :
new WrappedNodeExpr(facade.predicate),
read: facade.read ? new WrappedNodeExpr(facade.read) : null,
static: facade.static
};
}

Expand Down
2 changes: 2 additions & 0 deletions packages/compiler/src/render3/r3_identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ 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
15 changes: 15 additions & 0 deletions packages/compiler/src/render3/view/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,21 @@ export interface R3QueryMetadata {
* for a given node is to be returned.
*/
read: o.Expression|null;

/**
* Whether or not this query should collect only static results.
Comment thread
kara marked this conversation as resolved.
Resolved
*
* If static is true, the query's results will be set on the component after nodes are created,
* but before change detection runs. This means that any results that relied upon change detection
* to run (e.g. results inside *ngIf or *ngFor views) will not be collected. Query results are
* available in the ngOnInit hook.
*
* If static is false, the query's results will be set on the component after change detection
* runs. This means that the query results can contain nodes inside *ngIf or *ngFor views, but
* the results will not be available in the ngOnInit hook (only in the ngAfterContentInit for
* content hooks and ngAfterViewInit for view hooks).
*/
static: boolean;
}

/**
Expand Down
16 changes: 10 additions & 6 deletions packages/compiler/src/render3/view/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ function queriesFromGlobalMetadata(
first: query.first,
predicate: selectorsFromGlobalMetadata(query.selectors, outputCtx),
descendants: query.descendants, read,
static: !!query.static
};
});
}
Expand Down Expand Up @@ -490,10 +491,8 @@ function prepareQueryParams(query: R3QueryMetadata, constantPool: ConstantPool):
const parameters = [
getQueryPredicate(query, constantPool),
o.literal(query.descendants),
query.read || o.literal(null),
];
if (query.read) {
parameters.push(query.read);
}
return parameters;
}

Expand All @@ -519,9 +518,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 Expand Up @@ -590,9 +592,11 @@ function createViewQueriesFunction(
const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME);

meta.viewQueries.forEach((query: R3QueryMetadata) => {
const queryInstruction = query.static ? R3.staticViewQuery : R3.viewQuery;

// creation, e.g. r3.viewQuery(somePredicate, true);
const queryDefinition =
o.importExpr(R3.viewQuery).callFn(prepareQueryParams(query, constantPool));
o.importExpr(queryInstruction).callFn(prepareQueryParams(query, constantPool));
createStatements.push(queryDefinition.toStmt());

// update, e.g. (r3.queryRefresh(tmp = r3.loadViewQuery()) && (ctx.someDir = tmp));
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/compiler/compiler_facade_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export interface R3QueryMetadataFacade {
predicate: any|string[];
descendants: boolean;
read: any|null;
static: boolean;
}

export interface ParseSourceSpan {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/core_render3_private_export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export {
containerRefreshEnd as ɵcontainerRefreshEnd,
queryRefresh as ɵqueryRefresh,
viewQuery as ɵviewQuery,
staticViewQuery as ɵstaticViewQuery,
staticContentQuery as ɵstaticContentQuery,
loadViewQuery as ɵloadViewQuery,
contentQuery as ɵcontentQuery,
loadContentQuery as ɵloadContentQuery,
Expand Down
Loading