mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
fix(compiler): ensure that partially compiled queries can handle forward references (#44113)
When a partially compiled component or directive is "linked" in JIT mode, the body of its declaration is evaluated by the JavaScript runtime. If a class is referenced in a query (e.g. `ViewQuery` or `ContentQuery`) but its definition is later in the file, then the reference must be wrapped in a `forwardRef()` call. Previously, query predicates were not wrapped correctly in partial declarations causing the code to crash at runtime. In AOT mode, this code is never evaluated but instead transformed as part of the build, so this bug did not become apparent until Angular Material started running JIT mode tests on its distributable output. This change fixes this problem by noting when queries are wrapped in `forwardRef()` calls and ensuring that this gets passed through to partial compilation declarations and then suitably stripped during linking. See https://github.com/angular/components/pull/23882 and https://github.com/angular/components/issues/23907 PR Close #44113
This commit is contained in:
parent
67fbec39cb
commit
393efa54e6
21 changed files with 462 additions and 68 deletions
|
|
@ -5,7 +5,7 @@
|
|||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ChangeDetectionStrategy, compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig, makeBindingParser, outputAst as o, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3DeclareUsedDirectiveMetadata, R3PartialDeclaration, R3UsedDirectiveMetadata, ViewEncapsulation} from '@angular/compiler';
|
||||
import {ChangeDetectionStrategy, compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, ForwardRefHandling, InterpolationConfig, makeBindingParser, outputAst as o, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3DeclareUsedDirectiveMetadata, R3PartialDeclaration, R3UsedDirectiveMetadata, ViewEncapsulation} from '@angular/compiler';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
|
||||
import {Range} from '../../ast/ast_host';
|
||||
|
|
@ -69,8 +69,8 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
|
|||
const type = directiveExpr.getValue('type');
|
||||
const selector = directiveExpr.getString('selector');
|
||||
|
||||
const {expression: typeExpr, isForwardRef} = extractForwardRef(type);
|
||||
if (isForwardRef) {
|
||||
const {expression: typeExpr, forwardRef} = extractForwardRef(type);
|
||||
if (forwardRef === ForwardRefHandling.Unwrapped) {
|
||||
declarationListEmitMode = DeclarationListEmitMode.Closure;
|
||||
}
|
||||
|
||||
|
|
@ -101,8 +101,8 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
|
|||
let pipes = new Map<string, o.Expression>();
|
||||
if (metaObj.has('pipes')) {
|
||||
pipes = metaObj.getObject('pipes').toMap(pipe => {
|
||||
const {expression: pipeType, isForwardRef} = extractForwardRef(pipe);
|
||||
if (isForwardRef) {
|
||||
const {expression: pipeType, forwardRef} = extractForwardRef(pipe);
|
||||
if (forwardRef === ForwardRefHandling.Unwrapped) {
|
||||
declarationListEmitMode = DeclarationListEmitMode.Closure;
|
||||
}
|
||||
return pipeType;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {AstObject, AstValue} from '../../ast/ast_value';
|
|||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {wrapReference} from './util';
|
||||
import {extractForwardRef, wrapReference} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclareDirective()` call expressions.
|
||||
|
|
@ -140,7 +140,7 @@ function toQueryMetadata<TExpression>(obj: AstObject<R3DeclareQueryMetadata, TEx
|
|||
if (predicateExpr.isArray()) {
|
||||
predicate = predicateExpr.getArray().map(entry => entry.getString());
|
||||
} else {
|
||||
predicate = predicateExpr.getOpaque();
|
||||
predicate = extractForwardRef(predicateExpr);
|
||||
}
|
||||
return {
|
||||
propertyName: obj.getString('propertyName'),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {compileInjectable, ConstantPool, createMayBeForwardRefExpression, outputAst as o, R3DeclareInjectableMetadata, R3InjectableMetadata, R3PartialDeclaration} from '@angular/compiler';
|
||||
import {compileInjectable, ConstantPool, createMayBeForwardRefExpression, ForwardRefHandling, outputAst as o, R3DeclareInjectableMetadata, R3InjectableMetadata, R3PartialDeclaration} from '@angular/compiler';
|
||||
|
||||
import {AstObject} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
|
@ -43,8 +43,9 @@ export function toR3InjectableMeta<TExpression>(
|
|||
type: wrapReference(typeExpr.getOpaque()),
|
||||
internalType: typeExpr.getOpaque(),
|
||||
typeArgumentCount: 0,
|
||||
providedIn: metaObj.has('providedIn') ? extractForwardRef(metaObj.getValue('providedIn')) :
|
||||
createMayBeForwardRefExpression(o.literal(null), false),
|
||||
providedIn: metaObj.has('providedIn') ?
|
||||
extractForwardRef(metaObj.getValue('providedIn')) :
|
||||
createMayBeForwardRefExpression(o.literal(null), ForwardRefHandling.None),
|
||||
};
|
||||
|
||||
if (metaObj.has('useClass')) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {createMayBeForwardRefExpression, MaybeForwardRefExpression, outputAst as o, R3DeclareDependencyMetadata, R3DependencyMetadata, R3Reference} from '@angular/compiler';
|
||||
import {createMayBeForwardRefExpression, ForwardRefHandling, MaybeForwardRefExpression, outputAst as o, R3DeclareDependencyMetadata, R3DependencyMetadata, R3Reference} from '@angular/compiler';
|
||||
|
||||
import {AstObject, AstValue} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
|
@ -68,7 +68,7 @@ export function getDependency<TExpression>(
|
|||
export function extractForwardRef<TExpression>(expr: AstValue<unknown, TExpression>):
|
||||
MaybeForwardRefExpression<o.WrappedNodeExpr<TExpression>> {
|
||||
if (!expr.isCallExpression()) {
|
||||
return createMayBeForwardRefExpression(expr.getOpaque(), /* isForwardRef */ false);
|
||||
return createMayBeForwardRefExpression(expr.getOpaque(), ForwardRefHandling.None);
|
||||
}
|
||||
|
||||
const callee = expr.getCallee();
|
||||
|
|
@ -90,5 +90,6 @@ export function extractForwardRef<TExpression>(expr: AstValue<unknown, TExpressi
|
|||
wrapperFn, 'Unsupported `forwardRef(fn)` call, expected its argument to be a function');
|
||||
}
|
||||
|
||||
return createMayBeForwardRefExpression(wrapperFn.getFunctionReturnValue().getOpaque(), true);
|
||||
return createMayBeForwardRefExpression(
|
||||
wrapperFn.getFunctionReturnValue().getOpaque(), ForwardRefHandling.Unwrapped);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, emitDistinctChangesOnlyDefaultValue, Expression, ExternalExpr, FactoryTarget, getSafePropertyAccessString, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3ClassMetadata, R3DirectiveMetadata, R3FactoryMetadata, R3QueryMetadata, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, createMayBeForwardRefExpression, emitDistinctChangesOnlyDefaultValue, Expression, ExternalExpr, FactoryTarget, ForwardRefHandling, getSafePropertyAccessString, makeBindingParser, MaybeForwardRefExpression, ParsedHostBindings, ParseError, parseHostBindings, R3ClassMetadata, R3DirectiveMetadata, R3FactoryMetadata, R3QueryMetadata, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
|
||||
import ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
|
|
@ -531,17 +531,21 @@ export function extractQueryMetadata(
|
|||
ErrorCode.DECORATOR_ARITY_WRONG, exprNode, `@${name} must have arguments`);
|
||||
}
|
||||
const first = name === 'ViewChild' || name === 'ContentChild';
|
||||
const node = tryUnwrapForwardRef(args[0], reflector) ?? args[0];
|
||||
const forwardReferenceTarget = tryUnwrapForwardRef(args[0], reflector);
|
||||
const node = forwardReferenceTarget ?? args[0];
|
||||
|
||||
const arg = evaluator.evaluate(node);
|
||||
|
||||
/** Whether or not this query should collect only static results (see view/api.ts) */
|
||||
let isStatic: boolean = false;
|
||||
|
||||
// Extract the predicate
|
||||
let predicate: Expression|string[]|null = null;
|
||||
let predicate: MaybeForwardRefExpression|string[]|null = null;
|
||||
if (arg instanceof Reference || arg instanceof DynamicValue) {
|
||||
// References and predicates that could not be evaluated statically are emitted as is.
|
||||
predicate = new WrappedNodeExpr(node);
|
||||
predicate = createMayBeForwardRefExpression(
|
||||
new WrappedNodeExpr(node),
|
||||
forwardReferenceTarget !== null ? ForwardRefHandling.Unwrapped : ForwardRefHandling.None);
|
||||
} else if (typeof arg === 'string') {
|
||||
predicate = [arg];
|
||||
} else if (isStringArrayOrDie(arg, `@${name} predicate`, node)) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {compileClassMetadata, CompileClassMetadataFn, compileDeclareClassMetadata, compileDeclareInjectableFromMetadata, compileInjectable, createMayBeForwardRefExpression, FactoryTarget, LiteralExpr, MaybeForwardRefExpression, R3ClassMetadata, R3CompiledExpression, R3DependencyMetadata, R3InjectableMetadata, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {compileClassMetadata, CompileClassMetadataFn, compileDeclareClassMetadata, compileDeclareInjectableFromMetadata, compileInjectable, createMayBeForwardRefExpression, FactoryTarget, ForwardRefHandling, LiteralExpr, MaybeForwardRefExpression, R3ClassMetadata, R3CompiledExpression, R3DependencyMetadata, R3InjectableMetadata, WrappedNodeExpr} from '@angular/compiler';
|
||||
import ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
|
|
@ -162,7 +162,7 @@ function extractInjectableMetadata(
|
|||
type,
|
||||
typeArgumentCount,
|
||||
internalType,
|
||||
providedIn: createMayBeForwardRefExpression(new LiteralExpr(null), false),
|
||||
providedIn: createMayBeForwardRefExpression(new LiteralExpr(null), ForwardRefHandling.None),
|
||||
};
|
||||
} else if (decorator.args.length === 1) {
|
||||
const metaNode = decorator.args[0];
|
||||
|
|
@ -180,7 +180,7 @@ function extractInjectableMetadata(
|
|||
|
||||
const providedIn = meta.has('providedIn') ?
|
||||
getProviderExpression(meta.get('providedIn')!, reflector) :
|
||||
createMayBeForwardRefExpression(new LiteralExpr(null), false);
|
||||
createMayBeForwardRefExpression(new LiteralExpr(null), ForwardRefHandling.None);
|
||||
|
||||
let deps: R3DependencyMetadata[]|undefined = undefined;
|
||||
if ((meta.has('useClass') || meta.has('useFactory')) && meta.has('deps')) {
|
||||
|
|
@ -223,7 +223,8 @@ function getProviderExpression(
|
|||
expression: ts.Expression, reflector: ReflectionHost): MaybeForwardRefExpression {
|
||||
const forwardRefValue = tryUnwrapForwardRef(expression, reflector);
|
||||
return createMayBeForwardRefExpression(
|
||||
new WrappedNodeExpr(forwardRefValue ?? expression), forwardRefValue !== null);
|
||||
new WrappedNodeExpr(forwardRefValue ?? expression),
|
||||
forwardRefValue !== null ? ForwardRefHandling.Unwrapped : ForwardRefHandling.None);
|
||||
}
|
||||
|
||||
function extractInjectableCtorDeps(
|
||||
|
|
|
|||
|
|
@ -79,6 +79,92 @@ export declare class MyModule {
|
|||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: view_query_forward_ref.js
|
||||
****************************************************************************************************/
|
||||
import { Component, Directive, forwardRef, NgModule, ViewChild, ViewChildren } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class ViewQueryComponent {
|
||||
}
|
||||
ViewQueryComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ViewQueryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "someDir", first: true, predicate: i0.forwardRef(function () { return SomeDirective; }), descendants: true }, { propertyName: "someDirList", predicate: i0.forwardRef(function () { return SomeDirective; }), descendants: true }], ngImport: i0, template: `
|
||||
<div someDir></div>
|
||||
`, isInline: true, directives: [{ type: i0.forwardRef(function () { return SomeDirective; }), selector: "[someDir]" }] });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ViewQueryComponent, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'view-query-component',
|
||||
template: `
|
||||
<div someDir></div>
|
||||
`
|
||||
}]
|
||||
}], propDecorators: { someDir: [{
|
||||
type: ViewChild,
|
||||
args: [forwardRef(() => SomeDirective)]
|
||||
}], someDirList: [{
|
||||
type: ViewChildren,
|
||||
args: [forwardRef(() => SomeDirective)]
|
||||
}] } });
|
||||
export class MyApp {
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "my-app", ngImport: i0, template: `
|
||||
<view-query-component></view-query-component>
|
||||
`, isInline: true, components: [{ type: ViewQueryComponent, selector: "view-query-component" }] });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'my-app',
|
||||
template: `
|
||||
<view-query-component></view-query-component>
|
||||
`
|
||||
}]
|
||||
}] });
|
||||
export class SomeDirective {
|
||||
}
|
||||
SomeDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
||||
SomeDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", type: SomeDirective, selector: "[someDir]", ngImport: i0 });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeDirective, decorators: [{
|
||||
type: Directive,
|
||||
args: [{
|
||||
selector: '[someDir]',
|
||||
}]
|
||||
}] });
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [SomeDirective, ViewQueryComponent, MyApp] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, decorators: [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [SomeDirective, ViewQueryComponent, MyApp] }]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: view_query_forward_ref.d.ts
|
||||
****************************************************************************************************/
|
||||
import { QueryList } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class ViewQueryComponent {
|
||||
someDir: SomeDirective;
|
||||
someDirList: QueryList<SomeDirective>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<ViewQueryComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<ViewQueryComponent, "view-query-component", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyApp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "my-app", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class SomeDirective {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SomeDirective, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<SomeDirective, "[someDir]", never, {}, {}, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof SomeDirective, typeof ViewQueryComponent, typeof MyApp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: view_query_for_local_ref.js
|
||||
****************************************************************************************************/
|
||||
|
|
@ -410,6 +496,96 @@ export declare class MyModule {
|
|||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: content_query_forward_ref.js
|
||||
****************************************************************************************************/
|
||||
import { Component, ContentChild, ContentChildren, Directive, forwardRef, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class ContentQueryComponent {
|
||||
}
|
||||
ContentQueryComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ContentQueryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "someDir", first: true, predicate: i0.forwardRef(function () { return SomeDirective; }), descendants: true }, { propertyName: "someDirList", predicate: i0.forwardRef(function () { return SomeDirective; }) }], ngImport: i0, template: `
|
||||
<div><ng-content></ng-content></div>
|
||||
`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ContentQueryComponent, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'content-query-component',
|
||||
template: `
|
||||
<div><ng-content></ng-content></div>
|
||||
`
|
||||
}]
|
||||
}], propDecorators: { someDir: [{
|
||||
type: ContentChild,
|
||||
args: [forwardRef(() => SomeDirective)]
|
||||
}], someDirList: [{
|
||||
type: ContentChildren,
|
||||
args: [forwardRef(() => SomeDirective)]
|
||||
}] } });
|
||||
export class MyApp {
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "my-app", ngImport: i0, template: `
|
||||
<content-query-component>
|
||||
<div someDir></div>
|
||||
</content-query-component>
|
||||
`, isInline: true, components: [{ type: i0.forwardRef(function () { return ContentQueryComponent; }), selector: "content-query-component" }], directives: [{ type: i0.forwardRef(function () { return SomeDirective; }), selector: "[someDir]" }] });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'my-app',
|
||||
template: `
|
||||
<content-query-component>
|
||||
<div someDir></div>
|
||||
</content-query-component>
|
||||
`
|
||||
}]
|
||||
}] });
|
||||
export class SomeDirective {
|
||||
}
|
||||
SomeDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
||||
SomeDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", type: SomeDirective, selector: "[someDir]", ngImport: i0 });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeDirective, decorators: [{
|
||||
type: Directive,
|
||||
args: [{
|
||||
selector: '[someDir]',
|
||||
}]
|
||||
}] });
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [SomeDirective, ContentQueryComponent, MyApp] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, decorators: [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [SomeDirective, ContentQueryComponent, MyApp] }]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: content_query_forward_ref.d.ts
|
||||
****************************************************************************************************/
|
||||
import { QueryList } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class ContentQueryComponent {
|
||||
someDir: SomeDirective;
|
||||
someDirList: QueryList<SomeDirective>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<ContentQueryComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<ContentQueryComponent, "content-query-component", never, {}, {}, ["someDir", "someDirList"], ["*"]>;
|
||||
}
|
||||
export declare class MyApp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "my-app", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class SomeDirective {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SomeDirective, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<SomeDirective, "[someDir]", never, {}, {}, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof SomeDirective, typeof ContentQueryComponent, typeof MyApp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: content_query_for_local_ref.js
|
||||
****************************************************************************************************/
|
||||
|
|
|
|||
|
|
@ -16,6 +16,20 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support view queries with forwardRefs",
|
||||
"inputFiles": [
|
||||
"view_query_forward_ref.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid ViewQuery declaration",
|
||||
"files": [
|
||||
"view_query_forward_ref.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support view queries with local refs",
|
||||
"inputFiles": [
|
||||
|
|
@ -75,6 +89,20 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support content queries with forwardRefs",
|
||||
"inputFiles": [
|
||||
"content_query_forward_ref.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid ContentQuery declaration",
|
||||
"files": [
|
||||
"content_query_forward_ref.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support content queries with local refs",
|
||||
"inputFiles": [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
ContentQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: ContentQueryComponent,
|
||||
selectors: [["content-query-component"]],
|
||||
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, __QueryFlags.descendants__|__QueryFlags.emitDistinctChangesOnly__);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, __QueryFlags.emitDistinctChangesOnly__);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirList = $tmp$);
|
||||
}
|
||||
},
|
||||
ngContentSelectors: _c0,
|
||||
decls: 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
|
||||
});
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import {Component, ContentChild, ContentChildren, Directive, forwardRef, NgModule, QueryList} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'content-query-component',
|
||||
template: `
|
||||
<div><ng-content></ng-content></div>
|
||||
`
|
||||
})
|
||||
export class ContentQueryComponent {
|
||||
@ContentChild(forwardRef(() => SomeDirective)) someDir!: SomeDirective;
|
||||
@ContentChildren(forwardRef(() => SomeDirective)) someDirList!: QueryList<SomeDirective>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: `
|
||||
<content-query-component>
|
||||
<div someDir></div>
|
||||
</content-query-component>
|
||||
`
|
||||
})
|
||||
export class MyApp {
|
||||
}
|
||||
|
||||
|
||||
@Directive({
|
||||
selector: '[someDir]',
|
||||
})
|
||||
export class SomeDirective {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SomeDirective, ContentQueryComponent, MyApp]})
|
||||
export class MyModule {
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
ViewQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: ViewQueryComponent,
|
||||
selectors: [["view-query-component"]],
|
||||
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵviewQuery(SomeDirective, __QueryFlags.descendants__|__QueryFlags.emitDistinctChangesOnly__);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, __QueryFlags.descendants__|__QueryFlags.emitDistinctChangesOnly__);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first);
|
||||
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirList = $tmp$);
|
||||
}
|
||||
},
|
||||
// ...
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import {Component, Directive, forwardRef, NgModule, QueryList, ViewChild, ViewChildren} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'view-query-component',
|
||||
template: `
|
||||
<div someDir></div>
|
||||
`
|
||||
})
|
||||
export class ViewQueryComponent {
|
||||
@ViewChild(forwardRef(() => SomeDirective)) someDir!: SomeDirective;
|
||||
@ViewChildren(forwardRef(() => SomeDirective)) someDirList!: QueryList<SomeDirective>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: `
|
||||
<view-query-component></view-query-component>
|
||||
`
|
||||
})
|
||||
export class MyApp {
|
||||
}
|
||||
|
||||
|
||||
@Directive({
|
||||
selector: '[someDir]',
|
||||
})
|
||||
export class SomeDirective {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SomeDirective, ViewQueryComponent, MyApp]})
|
||||
export class MyModule {
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@ export {compileNgModule, R3NgModuleMetadata} from './render3/r3_module_compiler'
|
|||
export {compileInjector, R3InjectorMetadata} from './render3/r3_injector_compiler';
|
||||
export {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler';
|
||||
export {makeBindingParser, ParsedTemplate, parseTemplate, ParseTemplateOptions} from './render3/view/template';
|
||||
export {MaybeForwardRefExpression, R3CompiledExpression, R3Reference, createMayBeForwardRefExpression, devOnlyGuardedExpression, getSafePropertyAccessString} from './render3/util';
|
||||
export {ForwardRefHandling, MaybeForwardRefExpression, R3CompiledExpression, R3Reference, createMayBeForwardRefExpression, devOnlyGuardedExpression, getSafePropertyAccessString} from './render3/util';
|
||||
export {compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings, ParsedHostBindings, verifyHostBindings} from './render3/view/compiler';
|
||||
export {compileDeclareClassMetadata} from './render3/partial/class_metadata';
|
||||
export {compileDeclareComponentFromMetadata, DeclareComponentTemplateInfo} from './render3/partial/component';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import * as o from './output/output_ast';
|
||||
import {compileFactoryFunction, FactoryTarget, R3DependencyMetadata, R3FactoryDelegateType, R3FactoryMetadata} from './render3/r3_factory';
|
||||
import {Identifiers} from './render3/r3_identifiers';
|
||||
import {generateForwardRef, MaybeForwardRefExpression, R3CompiledExpression, R3Reference, typeWithParameters} from './render3/util';
|
||||
import {convertFromMaybeForwardRefExpression, ForwardRefHandling, generateForwardRef, MaybeForwardRefExpression, R3CompiledExpression, R3Reference, typeWithParameters} from './render3/util';
|
||||
import {DefinitionMap} from './render3/view/util';
|
||||
|
||||
export interface R3InjectableMetadata {
|
||||
|
|
@ -116,10 +116,7 @@ export function compileInjectable(
|
|||
|
||||
// Only generate providedIn property if it has a non-null value
|
||||
if ((meta.providedIn.expression as o.LiteralExpr).value !== null) {
|
||||
injectableProps.set(
|
||||
'providedIn',
|
||||
meta.providedIn.isForwardRef ? generateForwardRef(meta.providedIn.expression) :
|
||||
meta.providedIn.expression);
|
||||
injectableProps.set('providedIn', convertFromMaybeForwardRefExpression(meta.providedIn));
|
||||
}
|
||||
|
||||
const expression = o.importExpr(Identifiers.ɵɵdefineInjectable)
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@ import {compileInjector, R3InjectorMetadata} from './render3/r3_injector_compile
|
|||
import {R3JitReflector} from './render3/r3_jit';
|
||||
import {compileNgModule, compileNgModuleDeclarationExpression, R3NgModuleMetadata} from './render3/r3_module_compiler';
|
||||
import {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler';
|
||||
import {createMayBeForwardRefExpression, getSafePropertyAccessString, MaybeForwardRefExpression, wrapReference} from './render3/util';
|
||||
import {createMayBeForwardRefExpression, ForwardRefHandling, getSafePropertyAccessString, MaybeForwardRefExpression, wrapReference} from './render3/util';
|
||||
import {DeclarationListEmitMode, R3ComponentMetadata, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata, R3UsedDirectiveMetadata} from './render3/view/api';
|
||||
import {compileComponentFromMetadata, compileDirectiveFromMetadata, ParsedHostBindings, parseHostBindings, verifyHostBindings} from './render3/view/compiler';
|
||||
import {makeBindingParser, parseTemplate} from './render3/view/template';
|
||||
import {ResourceLoader} from './resource_loader';
|
||||
import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry';
|
||||
import {resolveForwardRef} from './util';
|
||||
|
||||
export class CompilerFacadeImpl implements CompilerFacade {
|
||||
FactoryTarget = FactoryTarget as any;
|
||||
|
|
@ -293,8 +294,7 @@ const USE_EXISTING = Object.keys({useExisting: null})[0];
|
|||
function convertToR3QueryMetadata(facade: R3QueryMetadataFacade): R3QueryMetadata {
|
||||
return {
|
||||
...facade,
|
||||
predicate: Array.isArray(facade.predicate) ? facade.predicate :
|
||||
new WrappedNodeExpr(facade.predicate),
|
||||
predicate: convertQueryPredicate(facade.predicate),
|
||||
read: facade.read ? new WrappedNodeExpr(facade.read) : null,
|
||||
static: facade.static,
|
||||
emitDistinctChangesOnly: facade.emitDistinctChangesOnly,
|
||||
|
|
@ -306,8 +306,7 @@ function convertQueryDeclarationToMetadata(declaration: R3DeclareQueryMetadataFa
|
|||
return {
|
||||
propertyName: declaration.propertyName,
|
||||
first: declaration.first ?? false,
|
||||
predicate: Array.isArray(declaration.predicate) ? declaration.predicate :
|
||||
new WrappedNodeExpr(declaration.predicate),
|
||||
predicate: convertQueryPredicate(declaration.predicate),
|
||||
descendants: declaration.descendants ?? false,
|
||||
read: declaration.read ? new WrappedNodeExpr(declaration.read) : null,
|
||||
static: declaration.static ?? false,
|
||||
|
|
@ -315,6 +314,15 @@ function convertQueryDeclarationToMetadata(declaration: R3DeclareQueryMetadataFa
|
|||
};
|
||||
}
|
||||
|
||||
function convertQueryPredicate(predicate: OpaqueValue|string[]): MaybeForwardRefExpression|
|
||||
string[] {
|
||||
return Array.isArray(predicate) ?
|
||||
// The predicate is an array of strings so pass it through.
|
||||
predicate :
|
||||
// The predicate is a type - assume that we will need to unwrap any `forwardRef()` calls.
|
||||
createMayBeForwardRefExpression(new WrappedNodeExpr(predicate), ForwardRefHandling.Wrapped);
|
||||
}
|
||||
|
||||
function convertDirectiveFacadeToMetadata(facade: R3DirectiveMetadataFacade): R3DirectiveMetadata {
|
||||
const inputsFromMetadata = parseInputOutputs(facade.inputs || []);
|
||||
const outputsFromMetadata = parseInputOutputs(facade.outputs || []);
|
||||
|
|
@ -475,13 +483,13 @@ type R3DirectiveMetadataFacadeNoPropAndWhitespace =
|
|||
* In JIT mode we do not want the compiler to wrap the expression in a `forwardRef()` call because,
|
||||
* if it is referencing a type that has not yet been defined, it will have already been wrapped in
|
||||
* a `forwardRef()` - either by the application developer or during partial-compilation. Thus we can
|
||||
* set `isForwardRef` to `false`.
|
||||
* use `ForwardRefHandling.None`.
|
||||
*/
|
||||
function convertToProviderExpression(obj: any, property: string): MaybeForwardRefExpression|
|
||||
undefined {
|
||||
if (obj.hasOwnProperty(property)) {
|
||||
return createMayBeForwardRefExpression(
|
||||
new WrappedNodeExpr(obj[property]), /* isForwardRef */ false);
|
||||
new WrappedNodeExpr(obj[property]), ForwardRefHandling.None);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -498,8 +506,8 @@ function wrapExpression(obj: any, property: string): WrappedNodeExpr<any>|undefi
|
|||
function computeProvidedIn(providedIn: Function|string|null|undefined): MaybeForwardRefExpression {
|
||||
const expression = typeof providedIn === 'function' ? new WrappedNodeExpr(providedIn) :
|
||||
new LiteralExpr(providedIn ?? null);
|
||||
// See `convertToProviderExpression()` for why `isForwardRef` is false.
|
||||
return createMayBeForwardRefExpression(expression, /* isForwardRef */ false);
|
||||
// See `convertToProviderExpression()` for why this uses `ForwardRefHandling.None`.
|
||||
return createMayBeForwardRefExpression(expression, ForwardRefHandling.None);
|
||||
}
|
||||
|
||||
function convertR3DependencyMetadataArray(facades: R3DependencyMetadataFacade[]|null|
|
||||
|
|
|
|||
|
|
@ -233,8 +233,8 @@ export interface R3DeclareQueryMetadata {
|
|||
first?: boolean;
|
||||
|
||||
/**
|
||||
* Either an expression representing a type or `InjectionToken` for the query
|
||||
* predicate, or a set of string selectors.
|
||||
* Either an expression representing a type (possibly wrapped in a `forwardRef()`) or
|
||||
* `InjectionToken` for the query predicate, or a set of string selectors.
|
||||
*/
|
||||
predicate: o.Expression|string[];
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
import * as o from '../../output/output_ast';
|
||||
import {Identifiers as R3} from '../r3_identifiers';
|
||||
import {R3CompiledExpression} from '../util';
|
||||
import {convertFromMaybeForwardRefExpression, R3CompiledExpression} from '../util';
|
||||
import {R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from '../view/api';
|
||||
import {createDirectiveType} from '../view/compiler';
|
||||
import {asLiteral, conditionallyCreateMapObjectLiteral, DefinitionMap} from '../view/util';
|
||||
|
|
@ -96,7 +96,9 @@ function compileQuery(query: R3QueryMetadata): o.LiteralMapExpr {
|
|||
meta.set('first', o.literal(true));
|
||||
}
|
||||
meta.set(
|
||||
'predicate', Array.isArray(query.predicate) ? asLiteral(query.predicate) : query.predicate);
|
||||
'predicate',
|
||||
Array.isArray(query.predicate) ? asLiteral(query.predicate) :
|
||||
convertFromMaybeForwardRefExpression(query.predicate));
|
||||
if (!query.emitDistinctChangesOnly) {
|
||||
// `emitDistinctChangesOnly` is special because we expect it to be `true`.
|
||||
// Therefore we explicitly emit the field, and explicitly place it only when it's `false`.
|
||||
|
|
|
|||
|
|
@ -94,48 +94,52 @@ export interface MaybeForwardRefExpression<T extends o.Expression = o.Expression
|
|||
*/
|
||||
expression: T;
|
||||
/**
|
||||
* If true, then the `expression` contains a reference to something that has not yet been
|
||||
* defined.
|
||||
* Specified whether the `expression` contains a reference to something that has not yet been
|
||||
* defined, and whether the expression is still wrapped in a `forwardRef()` call.
|
||||
*
|
||||
* This means that the expression must not be eagerly evaluated. Instead it must be wrapped in a
|
||||
* function closure that will be evaluated lazily to allow the definition of the expression to be
|
||||
* evaluated first.
|
||||
* If this value is `ForwardRefHandling.None` then the `expression` is safe to use as-is.
|
||||
*
|
||||
* In some cases the expression will naturally be placed inside such a function closure, such as
|
||||
* in a fully compiled factory function. In those cases nothing more needs to be done.
|
||||
* Otherwise the `expression` was wrapped in a call to `forwardRef()` and must not be eagerly
|
||||
* evaluated. Instead it must be wrapped in a function closure that will be evaluated lazily to
|
||||
* allow the definition of the expression to be evaluated first.
|
||||
*
|
||||
* In full AOT compilation it can be safe to unwrap the `forwardRef()` call up front if the
|
||||
* expression will actually be evaluated lazily inside a function call after the value of
|
||||
* `expression` has been defined.
|
||||
*
|
||||
* But in other cases, such as partial AOT compilation or JIT compilation the expression will be
|
||||
* evaluated eagerly in top level code so will need to continue to be wrapped in a `forwardRef()`
|
||||
* call.
|
||||
*
|
||||
* But in other cases, such as partial-compilation the expression will be located in top level
|
||||
* code so will need to be wrapped in a function that is passed to a `forwardRef()` call.
|
||||
*/
|
||||
isForwardRef: boolean;
|
||||
forwardRef: ForwardRefHandling;
|
||||
}
|
||||
|
||||
export function createMayBeForwardRefExpression<T extends o.Expression>(
|
||||
expression: T, isForwardRef: boolean): MaybeForwardRefExpression<T> {
|
||||
return {expression, isForwardRef};
|
||||
expression: T, forwardRef: ForwardRefHandling): MaybeForwardRefExpression<T> {
|
||||
return {expression, forwardRef};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a `MaybeForwardRefExpression` to an `Expression`, possibly wrapping its expression in a
|
||||
* `forwardRef()` call.
|
||||
*
|
||||
* If `MaybeForwardRefExpression.isForwardRef` is true then the expression was originally wrapped in
|
||||
* a `forwardRef()` call to prevent the value from being eagerly evaluated in the code.
|
||||
*
|
||||
* Normally, the linker will statically process the code, putting the `expression` inside a factory
|
||||
* function so the `forwardRef()` wrapper is not evaluated before it has been defined. But if the
|
||||
* partial declaration is evaluated by the JIT compiler the `forwardRef()` call is still needed to
|
||||
* prevent eager evaluation of the `expression`.
|
||||
*
|
||||
* So in partial declarations, expressions that could be forward-refs are wrapped in `forwardRef()`
|
||||
* calls, and this is then unwrapped in the linker as necessary.
|
||||
* If `MaybeForwardRefExpression.forwardRef` is `ForwardRefHandling.Unwrapped` then the expression
|
||||
* was originally wrapped in a `forwardRef()` call to prevent the value from being eagerly evaluated
|
||||
* in the code.
|
||||
*
|
||||
* See `packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts` and
|
||||
* `packages/compiler/src/jit_compiler_facade.ts` for more information.
|
||||
*/
|
||||
export function convertFromMaybeForwardRefExpression(
|
||||
{expression, isForwardRef}: MaybeForwardRefExpression): o.Expression {
|
||||
return isForwardRef ? generateForwardRef(expression) : expression;
|
||||
{expression, forwardRef}: MaybeForwardRefExpression): o.Expression {
|
||||
switch (forwardRef) {
|
||||
case ForwardRefHandling.None:
|
||||
case ForwardRefHandling.Wrapped:
|
||||
return expression;
|
||||
case ForwardRefHandling.Unwrapped:
|
||||
return generateForwardRef(expression);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -148,3 +152,15 @@ export function convertFromMaybeForwardRefExpression(
|
|||
export function generateForwardRef(expr: o.Expression): o.Expression {
|
||||
return o.importExpr(Identifiers.forwardRef).callFn([o.fn([], [new o.ReturnStatement(expr)])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies how a forward ref has been handled in a MaybeForwardRefExpression
|
||||
*/
|
||||
export const enum ForwardRefHandling {
|
||||
/** The expression was not wrapped in a `forwardRef()` call in the first place. */
|
||||
None,
|
||||
/** The expression is still wrapped in a `forwardRef()` call. */
|
||||
Wrapped,
|
||||
/** The expression was wrapped in a `forwardRef()` call but has since been unwrapped. */
|
||||
Unwrapped,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import * as o from '../../output/output_ast';
|
|||
import {ParseSourceSpan} from '../../parse_util';
|
||||
import * as t from '../r3_ast';
|
||||
import {R3DependencyMetadata} from '../r3_factory';
|
||||
import {R3Reference} from '../util';
|
||||
import {MaybeForwardRefExpression, R3Reference} from '../util';
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -302,7 +302,7 @@ export interface R3QueryMetadata {
|
|||
* Either an expression representing a type or `InjectionToken` for the query
|
||||
* predicate, or a set of string selectors.
|
||||
*/
|
||||
predicate: o.Expression|string[];
|
||||
predicate: MaybeForwardRefExpression|string[];
|
||||
|
||||
/**
|
||||
* Whether to include only direct children or all descendants.
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import * as o from '../../output/output_ast';
|
|||
import {ParseSourceSpan} from '../../parse_util';
|
||||
import {splitAtColon} from '../../util';
|
||||
import * as t from '../r3_ast';
|
||||
import {Identifiers as R3} from '../r3_identifiers';
|
||||
import {ForwardRefHandling} from '../util';
|
||||
|
||||
import {R3QueryMetadata} from './api';
|
||||
import {isI18nAttribute} from './i18n/util';
|
||||
|
|
@ -148,7 +150,14 @@ export function getQueryPredicate(
|
|||
});
|
||||
return constantPool.getConstLiteral(o.literalArr(predicate), true);
|
||||
} else {
|
||||
return query.predicate;
|
||||
// The original predicate may have been wrapped in a `forwardRef()` call.
|
||||
switch (query.predicate.forwardRef) {
|
||||
case ForwardRefHandling.None:
|
||||
case ForwardRefHandling.Unwrapped:
|
||||
return query.predicate.expression;
|
||||
case ForwardRefHandling.Wrapped:
|
||||
return o.importExpr(R3.resolveForwardRef).callFn([query.predicate.expression]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ElementRef, ɵɵngDeclareDirective} from '@angular/core';
|
||||
import {ElementRef, forwardRef, ɵɵngDeclareDirective} from '@angular/core';
|
||||
import {AttributeMarker, DirectiveDef} from '../../../src/render3';
|
||||
import {functionContaining} from './matcher';
|
||||
|
||||
|
|
@ -116,6 +116,27 @@ describe('directive declaration jit compilation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should compile content queries with forwardRefs', () => {
|
||||
const def = ɵɵngDeclareDirective({
|
||||
type: TestClass,
|
||||
queries: [
|
||||
{
|
||||
propertyName: 'byRef',
|
||||
predicate: forwardRef(() => Child),
|
||||
},
|
||||
],
|
||||
}) as DirectiveDef<TestClass>;
|
||||
|
||||
class Child {}
|
||||
|
||||
expectDirectiveDef(def, {
|
||||
contentQueries: functionContaining([
|
||||
/contentQuery[^(]*\(dirIndex,[^,]*resolveForwardRef[^,]*forward_ref[^,]*,[\s]*4\)/,
|
||||
'(ctx.byRef = _t)',
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile view queries', () => {
|
||||
const def = ɵɵngDeclareDirective({
|
||||
type: TestClass,
|
||||
|
|
@ -152,6 +173,27 @@ describe('directive declaration jit compilation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should compile view queries with forwardRefs', () => {
|
||||
const def = ɵɵngDeclareDirective({
|
||||
type: TestClass,
|
||||
viewQueries: [
|
||||
{
|
||||
propertyName: 'byRef',
|
||||
predicate: forwardRef(() => Child),
|
||||
},
|
||||
],
|
||||
}) as DirectiveDef<TestClass>;
|
||||
|
||||
class Child {}
|
||||
|
||||
expectDirectiveDef(def, {
|
||||
viewQuery: functionContaining([
|
||||
/viewQuery[^(]*\([^,]*resolveForwardRef[^,]*forward_ref[^,]*,[\s]*4\)/,
|
||||
'(ctx.byRef = _t)',
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile host bindings', () => {
|
||||
const def = ɵɵngDeclareDirective({
|
||||
type: TestClass,
|
||||
|
|
|
|||
Loading…
Reference in a new issue