Skip to content

Commit 3e3a1ef

Browse files
JoostKatscott
authored andcommitted
fix(ivy): support dynamic query tokens in AOT mode (#35307)
For view and content queries, the Ivy compiler attempts to statically evaluate the predicate token so that string predicates containing comma-separated reference names can be split into an array of strings during compilation. When the predicate is a dynamic value that cannot be statically interpreted at compile time, the compiler would previously produce an error. This behavior breaks a use-case where an `InjectionToken` is being used as query predicate, as the usage of the `new` keyword prevents such predicates from being statically evaluated. This commit changes the behavior to no longer produce an error for dynamic values. Instead, the expression is emitted as is into the generated code, postponing the evaluation to happen at runtime. Fixes #34267 Resolves FW-1828 PR Close #35307
1 parent 03d88c7 commit 3e3a1ef

File tree

3 files changed

+34
-5
lines changed

3 files changed

+34
-5
lines changed

packages/compiler-cli/src/ngtsc/annotations/src/directive.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,8 @@ export function extractQueryMetadata(
341341

342342
// Extract the predicate
343343
let predicate: Expression|string[]|null = null;
344-
if (arg instanceof Reference) {
344+
if (arg instanceof Reference || arg instanceof DynamicValue) {
345+
// References and predicates that could not be evaluated statically are emitted as is.
345346
predicate = new WrappedNodeExpr(node);
346347
} else if (typeof arg === 'string') {
347348
predicate = [arg];

packages/compiler-cli/test/ngtsc/fake_core/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export class ɵNgModuleFactory<T> {
5757
constructor(public clazz: T) {}
5858
}
5959

60+
export class InjectionToken<T> {
61+
constructor(description: string) {}
62+
}
63+
6064
export function forwardRef<T>(fn: () => T): T {
6165
return fn();
6266
}

packages/compiler-cli/test/ngtsc/ngtsc_spec.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ const trim = (input: string): string => input.replace(/\s+/g, ' ').trim();
2222

2323
const varRegExp = (name: string): RegExp => new RegExp(`var \\w+ = \\[\"${name}\"\\];`);
2424

25-
const viewQueryRegExp = (descend: boolean, ref?: string): RegExp => {
25+
const viewQueryRegExp = (predicate: string, descend: boolean, ref?: string): RegExp => {
2626
const maybeRef = ref ? `, ${ref}` : ``;
27-
return new RegExp(`i0\\.ɵɵviewQuery\\(\\w+, ${descend}${maybeRef}\\)`);
27+
return new RegExp(`i0\\.ɵɵviewQuery\\(${predicate}, ${descend}${maybeRef}\\)`);
2828
};
2929

3030
const contentQueryRegExp = (predicate: string, descend: boolean, ref?: string): RegExp => {
@@ -2396,7 +2396,7 @@ runInEachFileSystem(os => {
23962396
// match `i0.ɵɵcontentQuery(dirIndex, _c1, true, TemplateRef)`
23972397
expect(jsContents).toMatch(contentQueryRegExp('\\w+', true, 'TemplateRef'));
23982398
// match `i0.ɵɵviewQuery(_c2, true, null)`
2399-
expect(jsContents).toMatch(viewQueryRegExp(true));
2399+
expect(jsContents).toMatch(viewQueryRegExp('\\w+', true));
24002400
});
24012401

24022402
it('should generate queries for directives', () => {
@@ -2430,7 +2430,7 @@ runInEachFileSystem(os => {
24302430
// match `i0.ɵɵviewQuery(_c2, true)`
24312431
// Note that while ViewQuery doesn't necessarily make sense on a directive, because it doesn't
24322432
// have a view, we still need to handle it because a component could extend the directive.
2433-
expect(jsContents).toMatch(viewQueryRegExp(true));
2433+
expect(jsContents).toMatch(viewQueryRegExp('\\w+', true));
24342434
});
24352435

24362436
it('should handle queries that use forwardRef', () => {
@@ -2461,6 +2461,30 @@ runInEachFileSystem(os => {
24612461
expect(jsContents).toMatch(contentQueryRegExp('_c0', true));
24622462
});
24632463

2464+
it('should handle queries that use an InjectionToken', () => {
2465+
env.write(`test.ts`, `
2466+
import {Component, ContentChild, InjectionToken, ViewChild} from '@angular/core';
2467+
2468+
const TOKEN = new InjectionToken('token');
2469+
2470+
@Component({
2471+
selector: 'test',
2472+
template: '<div></div>',
2473+
})
2474+
class FooCmp {
2475+
@ViewChild(TOKEN as any) viewChild: any;
2476+
@ContentChild(TOKEN as any) contentChild: any;
2477+
}
2478+
`);
2479+
2480+
env.driveMain();
2481+
const jsContents = env.getContents('test.js');
2482+
// match `i0.ɵɵviewQuery(TOKEN, true, null)`
2483+
expect(jsContents).toMatch(viewQueryRegExp('TOKEN', true));
2484+
// match `i0.ɵɵcontentQuery(dirIndex, TOKEN, true, null)`
2485+
expect(jsContents).toMatch(contentQueryRegExp('TOKEN', true));
2486+
});
2487+
24642488
it('should compile expressions that write keys', () => {
24652489
env.write(`test.ts`, `
24662490
import {Component, ContentChild, TemplateRef, ViewContainerRef, forwardRef} from '@angular/core';

0 commit comments

Comments
 (0)