refactor(compiler): implement `ngDeclareInjectable()` (#41316)

This commit changes the partial compilation so that it outputs declarations
rather than definitions for injectables.

The JIT compiler and the linker are updated to be able to handle these
new declarations.

PR Close #41316
This commit is contained in:
Pete Bacon Darwin 2021-03-21 17:31:20 +00:00 committed by Zach Arend
parent c83fe1698b
commit 10a7c87692
41 changed files with 986 additions and 388 deletions

View File

@ -18,6 +18,7 @@ import {LinkerEnvironment} from '../linker_environment';
import {toR3DirectiveMeta} from './partial_directive_linker_1';
import {PartialLinker} from './partial_linker';
import {extractForwardRef} from './util';
/**
* A `PartialLinker` that is designed to process `ɵɵngDeclareComponent()` call expressions.
@ -81,10 +82,8 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
const type = directiveExpr.getValue('type');
const selector = directiveExpr.getString('selector');
let typeExpr = type.getOpaque();
const forwardRefType = extractForwardRef(type);
if (forwardRefType !== null) {
typeExpr = forwardRefType;
const {expression: typeExpr, isForwardRef} = extractForwardRef(type);
if (isForwardRef) {
declarationListEmitMode = DeclarationListEmitMode.Closure;
}
@ -115,13 +114,11 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
let pipes = new Map<string, o.Expression>();
if (metaObj.has('pipes')) {
pipes = metaObj.getObject('pipes').toMap(pipe => {
const forwardRefType = extractForwardRef(pipe);
if (forwardRefType !== null) {
const {expression: pipeType, isForwardRef} = extractForwardRef(pipe);
if (isForwardRef) {
declarationListEmitMode = DeclarationListEmitMode.Closure;
return forwardRefType;
} else {
return pipe.getOpaque();
}
return pipeType;
});
}
@ -277,35 +274,3 @@ function parseChangeDetectionStrategy<TExpression>(
}
return enumValue;
}
/**
* Extract the type reference expression from a `forwardRef` function call. For example, the
* expression `forwardRef(function() { return FooDir; })` returns `FooDir`. Note that this
* expression is required to be wrapped in a closure, as otherwise the forward reference would be
* resolved before initialization.
*/
function extractForwardRef<TExpression>(expr: AstValue<unknown, TExpression>):
o.WrappedNodeExpr<TExpression>|null {
if (!expr.isCallExpression()) {
return null;
}
const callee = expr.getCallee();
if (callee.getSymbolName() !== 'forwardRef') {
throw new FatalLinkerError(
callee.expression, 'Unsupported directive type, expected forwardRef or a type reference');
}
const args = expr.getArguments();
if (args.length !== 1) {
throw new FatalLinkerError(expr, 'Unsupported forwardRef call, expected a single argument');
}
const wrapperFn = args[0] as AstValue<Function, TExpression>;
if (!wrapperFn.isFunction()) {
throw new FatalLinkerError(
wrapperFn, 'Unsupported forwardRef call, expected a function argument');
}
return wrapperFn.getFunctionReturnValue().getOpaque();
}

View File

@ -5,14 +5,14 @@
* 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 {compileFactoryFunction, ConstantPool, FactoryTarget, R3DeclareDependencyMetadata, R3DeclareFactoryMetadata, R3DependencyMetadata, R3FactoryMetadata, R3PartialDeclaration} from '@angular/compiler';
import {compileFactoryFunction, ConstantPool, FactoryTarget, R3DeclareFactoryMetadata, R3DependencyMetadata, R3FactoryMetadata, R3PartialDeclaration} from '@angular/compiler';
import * as o from '@angular/compiler/src/output/output_ast';
import {AstObject} from '../../ast/ast_value';
import {FatalLinkerError} from '../../fatal_linker_error';
import {PartialLinker} from './partial_linker';
import {parseEnum, wrapReference} from './util';
import {getDependency, parseEnum, wrapReference} from './util';
/**
* A `PartialLinker` that is designed to process `ɵɵngDeclareFactory()` call expressions.
@ -45,11 +45,11 @@ export function toR3FactoryMeta<TExpression>(
internalType: metaObj.getOpaque('type'),
typeArgumentCount: 0,
target: parseEnum(metaObj.getValue('target'), FactoryTarget),
deps: getDeps(metaObj, 'deps'),
deps: getDependencies(metaObj, 'deps'),
};
}
function getDeps<TExpression>(
function getDependencies<TExpression>(
metaObj: AstObject<R3DeclareFactoryMetadata, TExpression>,
propName: keyof R3DeclareFactoryMetadata): R3DependencyMetadata[]|null|'invalid' {
if (!metaObj.has(propName)) {
@ -57,32 +57,10 @@ function getDeps<TExpression>(
}
const deps = metaObj.getValue(propName);
if (deps.isArray()) {
return deps.getArray().map(dep => getDep(dep.getObject()));
return deps.getArray().map(dep => getDependency(dep.getObject()));
}
if (deps.isString()) {
return 'invalid';
}
return null;
}
function getDep<TExpression>(depObj: AstObject<R3DeclareDependencyMetadata, TExpression>):
R3DependencyMetadata {
const isAttribute = depObj.has('attribute') && depObj.getBoolean('attribute');
const token = depObj.getOpaque('token');
// Normally `attribute` is a string literal and so its `attributeNameType` is the same string
// literal. If the `attribute` is some other expression, the `attributeNameType` would be the
// `unknown` type. It is not possible to generate this when linking, since it only deals with JS
// and not typings. When linking the existence of the `attributeNameType` only acts as a marker to
// change the injection instruction that is generated, so we just pass the literal string
// `"unknown"`.
const attributeNameType = isAttribute ? o.literal('unknown') : null;
const dep: R3DependencyMetadata = {
token,
attributeNameType,
host: depObj.has('host') && depObj.getBoolean('host'),
optional: depObj.has('optional') && depObj.getBoolean('optional'),
self: depObj.has('self') && depObj.getBoolean('self'),
skipSelf: depObj.has('skipSelf') && depObj.getBoolean('skipSelf'),
};
return dep;
}

View File

@ -0,0 +1,69 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* 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, createR3ProviderExpression, R3DeclareInjectableMetadata, R3InjectableMetadata, R3PartialDeclaration} from '@angular/compiler';
import * as o from '@angular/compiler/src/output/output_ast';
import {AstObject} from '../../ast/ast_value';
import {FatalLinkerError} from '../../fatal_linker_error';
import {PartialLinker} from './partial_linker';
import {extractForwardRef, getDependency, wrapReference} from './util';
/**
* A `PartialLinker` that is designed to process `ɵɵngDeclareInjectable()` call expressions.
*/
export class PartialInjectableLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
linkPartialDeclaration(
constantPool: ConstantPool,
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
const meta = toR3InjectableMeta(metaObj);
const def = compileInjectable(meta, /* resolveForwardRefs */ false);
return def.expression;
}
}
/**
* Derives the `R3InjectableMetadata` structure from the AST object.
*/
export function toR3InjectableMeta<TExpression>(
metaObj: AstObject<R3DeclareInjectableMetadata, TExpression>): R3InjectableMetadata {
const typeExpr = metaObj.getValue('type');
const typeName = typeExpr.getSymbolName();
if (typeName === null) {
throw new FatalLinkerError(
typeExpr.expression, 'Unsupported type, its name could not be determined');
}
const meta: R3InjectableMetadata = {
name: typeName,
type: wrapReference(typeExpr.getOpaque()),
internalType: typeExpr.getOpaque(),
typeArgumentCount: 0,
providedIn: metaObj.has('providedIn') ? extractForwardRef(metaObj.getValue('providedIn')) :
createR3ProviderExpression(o.literal(null), false),
};
if (metaObj.has('useClass')) {
meta.useClass = extractForwardRef(metaObj.getValue('useClass'));
}
if (metaObj.has('useFactory')) {
meta.useFactory = metaObj.getOpaque('useFactory');
}
if (metaObj.has('useExisting')) {
meta.useExisting = extractForwardRef(metaObj.getValue('useExisting'));
}
if (metaObj.has('useValue')) {
meta.useValue = extractForwardRef(metaObj.getValue('useValue'));
}
if (metaObj.has('deps')) {
meta.deps = metaObj.getArray('deps').map(dep => getDependency(dep.getObject()));
}
return meta;
}

View File

@ -14,6 +14,7 @@ import {LinkerEnvironment} from '../linker_environment';
import {PartialComponentLinkerVersion1} from './partial_component_linker_1';
import {PartialDirectiveLinkerVersion1} from './partial_directive_linker_1';
import {PartialFactoryLinkerVersion1} from './partial_factory_linker_1';
import {PartialInjectableLinkerVersion1} from './partial_injectable_linker_1';
import {PartialInjectorLinkerVersion1} from './partial_injector_linker_1';
import {PartialLinker} from './partial_linker';
import {PartialNgModuleLinkerVersion1} from './partial_ng_module_linker_1';
@ -22,12 +23,13 @@ import {PartialPipeLinkerVersion1} from './partial_pipe_linker_1';
export const ɵɵngDeclareDirective = 'ɵɵngDeclareDirective';
export const ɵɵngDeclareComponent = 'ɵɵngDeclareComponent';
export const ɵɵngDeclareFactory = 'ɵɵngDeclareFactory';
export const ɵɵngDeclareInjectable = 'ɵɵngDeclareInjectable';
export const ɵɵngDeclareInjector = 'ɵɵngDeclareInjector';
export const ɵɵngDeclareNgModule = 'ɵɵngDeclareNgModule';
export const ɵɵngDeclarePipe = 'ɵɵngDeclarePipe';
export const declarationFunctions = [
ɵɵngDeclareDirective, ɵɵngDeclareComponent, ɵɵngDeclareFactory, ɵɵngDeclareInjector,
ɵɵngDeclareNgModule, ɵɵngDeclarePipe
ɵɵngDeclareDirective, ɵɵngDeclareComponent, ɵɵngDeclareFactory, ɵɵngDeclareInjectable,
ɵɵngDeclareInjector, ɵɵngDeclareNgModule, ɵɵngDeclarePipe
];
interface LinkerRange<TExpression> {
@ -93,6 +95,7 @@ export class PartialLinkerSelector<TStatement, TExpression> {
environment, createGetSourceFile(sourceUrl, code, environment.sourceFileLoader), sourceUrl,
code);
const partialFactoryLinkerVersion1 = new PartialFactoryLinkerVersion1();
const partialInjectableLinkerVersion1 = new PartialInjectableLinkerVersion1();
const partialInjectorLinkerVersion1 = new PartialInjectorLinkerVersion1();
const partialNgModuleLinkerVersion1 =
new PartialNgModuleLinkerVersion1(environment.options.linkerJitMode);
@ -111,6 +114,10 @@ export class PartialLinkerSelector<TStatement, TExpression> {
{range: '0.0.0-PLACEHOLDER', linker: partialFactoryLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialFactoryLinkerVersion1},
]);
linkers.set(ɵɵngDeclareInjectable, [
{range: '0.0.0-PLACEHOLDER', linker: partialInjectableLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialInjectableLinkerVersion1},
]);
linkers.set(ɵɵngDeclareInjector, [
{range: '0.0.0-PLACEHOLDER', linker: partialInjectorLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialInjectorLinkerVersion1},

View File

@ -5,9 +5,10 @@
* 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 {R3Reference} from '@angular/compiler';
import {createR3ProviderExpression, R3DeclareDependencyMetadata, R3DependencyMetadata, R3ProviderExpression, R3Reference} from '@angular/compiler';
import * as o from '@angular/compiler/src/output/output_ast';
import {AstValue} from '../../ast/ast_value';
import {AstObject, AstValue} from '../../ast/ast_value';
import {FatalLinkerError} from '../../fatal_linker_error';
export function wrapReference<TExpression>(wrapped: o.WrappedNodeExpr<TExpression>): R3Reference {
@ -29,3 +30,66 @@ export function parseEnum<TExpression, TEnum>(
}
return enumValue;
}
/**
* Parse a dependency structure from an AST object.
*/
export function getDependency<TExpression>(
depObj: AstObject<R3DeclareDependencyMetadata, TExpression>): R3DependencyMetadata {
const isAttribute = depObj.has('attribute') && depObj.getBoolean('attribute');
const token = depObj.getOpaque('token');
// Normally `attribute` is a string literal and so its `attributeNameType` is the same string
// literal. If the `attribute` is some other expression, the `attributeNameType` would be the
// `unknown` type. It is not possible to generate this when linking, since it only deals with JS
// and not typings. When linking the existence of the `attributeNameType` only acts as a marker to
// change the injection instruction that is generated, so we just pass the literal string
// `"unknown"`.
const attributeNameType = isAttribute ? o.literal('unknown') : null;
return {
token,
attributeNameType,
host: depObj.has('host') && depObj.getBoolean('host'),
optional: depObj.has('optional') && depObj.getBoolean('optional'),
self: depObj.has('self') && depObj.getBoolean('self'),
skipSelf: depObj.has('skipSelf') && depObj.getBoolean('skipSelf'),
};
}
/**
* Return an `R3ProviderExpression` that represents either the extracted type reference expression
* from a `forwardRef` function call, or the type itself.
*
* For example, the expression `forwardRef(function() { return FooDir; })` returns `FooDir`. Note
* that this expression is required to be wrapped in a closure, as otherwise the forward reference
* would be resolved before initialization.
*
* If there is no forwardRef call expression then we just return the opaque type.
*/
export function extractForwardRef<TExpression>(expr: AstValue<unknown, TExpression>):
R3ProviderExpression<o.WrappedNodeExpr<TExpression>> {
if (!expr.isCallExpression()) {
return createR3ProviderExpression(expr.getOpaque(), /* isForwardRef */ false);
}
const callee = expr.getCallee();
if (callee.getSymbolName() !== 'forwardRef') {
throw new FatalLinkerError(
callee.expression,
'Unsupported expression, expected a `forwardRef()` call or a type reference');
}
const args = expr.getArguments();
if (args.length !== 1) {
throw new FatalLinkerError(
expr, 'Unsupported `forwardRef(fn)` call, expected a single argument');
}
const wrapperFn = args[0] as AstValue<Function, TExpression>;
if (!wrapperFn.isFunction()) {
throw new FatalLinkerError(
wrapperFn, 'Unsupported `forwardRef(fn)` call, expected its argument to be a function');
}
return createR3ProviderExpression(wrapperFn.getFunctionReturnValue().getOpaque(), true);
}

View File

@ -17,6 +17,7 @@ import {LinkerEnvironment} from '../../../src/file_linker/linker_environment';
import {PartialComponentLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_component_linker_1';
import {PartialDirectiveLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_directive_linker_1';
import {PartialFactoryLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_factory_linker_1';
import {PartialInjectableLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_injectable_linker_1';
import {PartialInjectorLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_injector_linker_1';
import {PartialLinkerSelector} from '../../../src/file_linker/partial_linkers/partial_linker_selector';
import {PartialNgModuleLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_ng_module_linker_1';
@ -43,6 +44,7 @@ describe('PartialLinkerSelector', () => {
expect(selector.supportsDeclaration('ɵɵngDeclareDirective')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareComponent')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareFactory')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareInjectable')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareInjector')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareNgModule')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclarePipe')).toBe(true);
@ -66,6 +68,8 @@ describe('PartialLinkerSelector', () => {
.toBeInstanceOf(PartialComponentLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareFactory', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialFactoryLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareInjectable', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialInjectableLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareInjector', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialInjectorLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareNgModule', '0.0.0-PLACEHOLDER'))

View File

@ -24,7 +24,7 @@ import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFl
import {createValueHasWrongTypeError, getDirectiveDiagnostics, getProviderDiagnostics, getUndecoratedClassWithAngularFeaturesDiagnostic} from './diagnostics';
import {compileDeclareFactory, compileNgFactoryDefField} from './factory';
import {generateSetClassMetadataCall} from './metadata';
import {compileResults, createSourceSpan, findAngularDecorator, getConstructorDependencies, isAngularDecorator, readBaseClass, resolveProvidersRequiringFactory, toFactoryMetadata, unwrapConstructorDependencies, unwrapExpression, unwrapForwardRef, validateConstructorDependencies, wrapFunctionExpressionsInParens, wrapTypeReference} from './util';
import {compileResults, createSourceSpan, findAngularDecorator, getConstructorDependencies, isAngularDecorator, readBaseClass, resolveProvidersRequiringFactory, toFactoryMetadata, tryUnwrapForwardRef, unwrapConstructorDependencies, unwrapExpression, validateConstructorDependencies, wrapFunctionExpressionsInParens, wrapTypeReference} from './util';
const EMPTY_OBJECT: {[key: string]: string} = {};
const FIELD_DECORATORS = [
@ -528,7 +528,7 @@ export function extractQueryMetadata(
ErrorCode.DECORATOR_ARITY_WRONG, exprNode, `@${name} must have arguments`);
}
const first = name === 'ViewChild' || name === 'ContentChild';
const node = unwrapForwardRef(args[0], reflector);
const node = tryUnwrapForwardRef(args[0], reflector) ?? args[0];
const arg = evaluator.evaluate(node);
/** Whether or not this query should collect only static results (see view/api.ts) */

View File

@ -10,6 +10,8 @@ import {compileDeclareFactoryFunction, compileFactoryFunction, R3FactoryMetadata
import {CompileResult} from '../../transform';
export type CompileFactoryFn = (metadata: R3FactoryMetadata) => CompileResult;
export function compileNgFactoryDefField(metadata: R3FactoryMetadata): CompileResult {
const res = compileFactoryFunction(metadata);
return {name: 'ɵfac', initializer: res.expression, statements: res.statements, type: res.type};

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {compileInjectable as compileIvyInjectable, Expression, FactoryTarget, LiteralExpr, R3DependencyMetadata, R3FactoryMetadata, R3InjectableMetadata, Statement, WrappedNodeExpr} from '@angular/compiler';
import {compileDeclareInjectableFromMetadata, compileInjectable, createR3ProviderExpression, Expression, FactoryTarget, LiteralExpr, R3CompiledExpression, R3DependencyMetadata, R3InjectableMetadata, R3ProviderExpression, Statement, WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
@ -16,9 +16,9 @@ import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
import {compileDeclareFactory, compileNgFactoryDefField} from './factory';
import {compileDeclareFactory, CompileFactoryFn, compileNgFactoryDefField} from './factory';
import {generateSetClassMetadataCall} from './metadata';
import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, toFactoryMetadata, unwrapConstructorDependencies, unwrapForwardRef, validateConstructorDependencies, wrapTypeReference} from './util';
import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, toFactoryMetadata, tryUnwrapForwardRef, unwrapConstructorDependencies, validateConstructorDependencies, wrapTypeReference} from './util';
export interface InjectableHandlerData {
meta: R3InjectableMetadata;
@ -28,7 +28,7 @@ export interface InjectableHandlerData {
}
/**
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
* Adapts the `compileInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
*/
export class InjectableDecoratorHandler implements
DecoratorHandler<Decorator, InjectableHandlerData, null, unknown> {
@ -95,45 +95,25 @@ export class InjectableDecoratorHandler implements
}
compileFull(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
const res = compileIvyInjectable(analysis.meta);
const statements = res.statements;
const results: CompileResult[] = [];
if (analysis.needsFactory) {
const meta = analysis.meta;
const factoryRes = compileNgFactoryDefField(
toFactoryMetadata({...meta, deps: analysis.ctorDeps}, FactoryTarget.Injectable));
if (analysis.metadataStmt !== null) {
factoryRes.statements.push(analysis.metadataStmt);
}
results.push(factoryRes);
}
const ɵprov = this.reflector.getMembersOfClass(node).find(member => member.name === 'ɵprov');
if (ɵprov !== undefined && this.errorOnDuplicateProv) {
throw new FatalDiagnosticError(
ErrorCode.INJECTABLE_DUPLICATE_PROV, ɵprov.nameNode || ɵprov.node || node,
'Injectables cannot contain a static ɵprov property, because the compiler is going to generate one.');
}
if (ɵprov === undefined) {
// Only add a new ɵprov if there is not one already
results.push({name: 'ɵprov', initializer: res.expression, statements, type: res.type});
}
return results;
return this.compile(
compileNgFactoryDefField, meta => compileInjectable(meta, false), node, analysis);
}
compilePartial(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>):
CompileResult[] {
const res = compileIvyInjectable(analysis.meta);
const statements = res.statements;
return this.compile(
compileDeclareFactory, compileDeclareInjectableFromMetadata, node, analysis);
}
private compile(
compileFactoryFn: CompileFactoryFn,
compileInjectableFn: (meta: R3InjectableMetadata) => R3CompiledExpression,
node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
const results: CompileResult[] = [];
if (analysis.needsFactory) {
const meta = analysis.meta;
const factoryRes = compileDeclareFactory(
const factoryRes = compileFactoryFn(
toFactoryMetadata({...meta, deps: analysis.ctorDeps}, FactoryTarget.Injectable));
if (analysis.metadataStmt !== null) {
factoryRes.statements.push(analysis.metadataStmt);
@ -150,7 +130,9 @@ export class InjectableDecoratorHandler implements
if (ɵprov === undefined) {
// Only add a new ɵprov if there is not one already
results.push({name: 'ɵprov', initializer: res.expression, statements, type: res.type});
const res = compileInjectableFn(analysis.meta);
results.push(
{name: 'ɵprov', initializer: res.expression, statements: res.statements, type: res.type});
}
return results;
@ -159,7 +141,7 @@ export class InjectableDecoratorHandler implements
/**
* Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the
* input metadata needed to run `compileIvyInjectable`.
* input metadata needed to run `compileInjectable`.
*
* A `null` return value indicates this is @Injectable has invalid data.
*/
@ -181,7 +163,7 @@ function extractInjectableMetadata(
type,
typeArgumentCount,
internalType,
providedIn: new LiteralExpr(null),
providedIn: createR3ProviderExpression(new LiteralExpr(null), false),
};
} else if (decorator.args.length === 1) {
const metaNode = decorator.args[0];
@ -196,12 +178,12 @@ function extractInjectableMetadata(
// Resolve the fields of the literal into a map of field name to expression.
const meta = reflectObjectLiteral(metaNode);
let providedIn: Expression = new LiteralExpr(null);
if (meta.has('providedIn')) {
providedIn = new WrappedNodeExpr(meta.get('providedIn')!);
}
let userDeps: R3DependencyMetadata[]|undefined = undefined;
const providedIn = meta.has('providedIn') ?
getProviderExpression(meta.get('providedIn')!, reflector) :
createR3ProviderExpression(new LiteralExpr(null), false);
let deps: R3DependencyMetadata[]|undefined = undefined;
if ((meta.has('useClass') || meta.has('useFactory')) && meta.has('deps')) {
const depsExpr = meta.get('deps')!;
if (!ts.isArrayLiteralExpression(depsExpr)) {
@ -209,58 +191,42 @@ function extractInjectableMetadata(
ErrorCode.VALUE_NOT_LITERAL, depsExpr,
`@Injectable deps metadata must be an inline array`);
}
userDeps = depsExpr.elements.map(dep => getDep(dep, reflector));
deps = depsExpr.elements.map(dep => getDep(dep, reflector));
}
const result: R3InjectableMetadata = {name, type, typeArgumentCount, internalType, providedIn};
if (meta.has('useValue')) {
return {
name,
type,
typeArgumentCount,
internalType,
providedIn,
useValue: new WrappedNodeExpr(unwrapForwardRef(meta.get('useValue')!, reflector)),
};
result.useValue = getProviderExpression(meta.get('useValue')!, reflector);
} else if (meta.has('useExisting')) {
return {
name,
type,
typeArgumentCount,
internalType,
providedIn,
useExisting: new WrappedNodeExpr(unwrapForwardRef(meta.get('useExisting')!, reflector)),
};
result.useExisting = getProviderExpression(meta.get('useExisting')!, reflector);
} else if (meta.has('useClass')) {
return {
name,
type,
typeArgumentCount,
internalType,
providedIn,
useClass: new WrappedNodeExpr(unwrapForwardRef(meta.get('useClass')!, reflector)),
userDeps,
};
result.useClass = getProviderExpression(meta.get('useClass')!, reflector);
result.deps = deps;
} else if (meta.has('useFactory')) {
// useFactory is special - the 'deps' property must be analyzed.
const factory = new WrappedNodeExpr(meta.get('useFactory')!);
return {
name,
type,
typeArgumentCount,
internalType,
providedIn,
useFactory: factory,
userDeps,
};
} else {
return {name, type, typeArgumentCount, internalType, providedIn};
result.useFactory = new WrappedNodeExpr(meta.get('useFactory')!);
result.deps = deps;
}
return result;
} else {
throw new FatalDiagnosticError(
ErrorCode.DECORATOR_ARITY_WRONG, decorator.args[2], 'Too many arguments to @Injectable');
}
}
/**
* Get the `R3ProviderExpression` for this `expression`.
*
* The `useValue`, `useExisting` and `useClass` properties might be wrapped in a `ForwardRef`, which
* needs to be unwrapped. This function will do that unwrapping and set a flag on the returned
* object to indicate whether the value needed unwrapping.
*/
function getProviderExpression(
expression: ts.Expression, reflector: ReflectionHost): R3ProviderExpression {
const forwardRefValue = tryUnwrapForwardRef(expression, reflector);
return createR3ProviderExpression(
new WrappedNodeExpr(forwardRefValue ?? expression), forwardRefValue !== null);
}
function extractInjectableCtorDeps(
clazz: ClassDeclaration, meta: R3InjectableMetadata, decorator: Decorator,
reflector: ReflectionHost, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,

View File

@ -332,36 +332,40 @@ function expandForwardRef(arg: ts.Expression): ts.Expression|null {
}
}
/**
* Possibly resolve a forwardRef() expression into the inner value.
* If the given `node` is a forwardRef() expression then resolve its inner value, otherwise return
* `null`.
*
* @param node the forwardRef() expression to resolve
* @param reflector a ReflectionHost
* @returns the resolved expression, if the original expression was a forwardRef(), or the original
* expression otherwise
* @returns the resolved expression, if the original expression was a forwardRef(), or `null`
* otherwise.
*/
export function unwrapForwardRef(node: ts.Expression, reflector: ReflectionHost): ts.Expression {
export function tryUnwrapForwardRef(node: ts.Expression, reflector: ReflectionHost): ts.Expression|
null {
node = unwrapExpression(node);
if (!ts.isCallExpression(node) || node.arguments.length !== 1) {
return node;
return null;
}
const fn =
ts.isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression;
if (!ts.isIdentifier(fn)) {
return node;
return null;
}
const expr = expandForwardRef(node.arguments[0]);
if (expr === null) {
return node;
return null;
}
const imp = reflector.getImportOfIdentifier(fn);
if (imp === null || imp.from !== '@angular/core' || imp.name !== 'forwardRef') {
return node;
} else {
return expr;
return null;
}
return expr;
}
/**

View File

@ -167,7 +167,7 @@ import * as i0 from "@angular/core";
export class Thing {
}
Thing.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Thing, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
Thing.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: Thing, factory: Thing.ɵfac });
Thing.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Thing });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Thing, [{
type: Injectable
}], null, null); })();
@ -178,14 +178,14 @@ export class BaseService {
;
}
BaseService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: BaseService, deps: [{ token: Thing }], target: i0.ɵɵFactoryTarget.Injectable });
BaseService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: BaseService, factory: BaseService.ɵfac });
BaseService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: BaseService });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BaseService, [{
type: Injectable
}], function () { return [{ type: Thing }]; }, null); })();
export class ChildService extends BaseService {
}
ChildService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ChildService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
ChildService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: ChildService, factory: ChildService.ɵfac });
ChildService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ChildService });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ChildService, [{
type: Injectable
}], null, null); })();
@ -469,7 +469,7 @@ import * as i0 from "@angular/core";
export class Service {
}
Service.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Service, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
Service.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: Service, factory: Service.ɵfac });
Service.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Service });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Service, [{
type: Injectable
}], null, null); })();

View File

@ -6,7 +6,7 @@ import * as i0 from "@angular/core";
export class MyService {
}
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
MyService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyService, factory: MyService.ɵfac });
MyService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable
}], null, null); })();
@ -80,7 +80,7 @@ export class MyService {
constructor(dep) { }
}
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [{ token: MyDependency }], target: i0.ɵɵFactoryTarget.Injectable });
MyService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyService, factory: MyService.ɵfac });
MyService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable
}], function () { return [{ type: MyDependency }]; }, null); })();
@ -111,7 +111,7 @@ export class MyService {
constructor(dep, optionalDep) { }
}
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [{ token: MyDependency }, { token: MyOptionalDependency, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
MyService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyService, factory: MyService.ɵfac });
MyService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable
}], function () { return [{ type: MyDependency }, { type: MyOptionalDependency, decorators: [{
@ -144,7 +144,7 @@ function alternateFactory() {
export class MyService {
}
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
MyService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyService, factory: function () { return alternateFactory(); }, providedIn: 'root' });
MyService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, providedIn: 'root', useFactory: alternateFactory });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable,
args: [{ providedIn: 'root', useFactory: alternateFactory }]
@ -171,12 +171,7 @@ class MyAlternateService {
export class MyService {
}
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
MyService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyService, factory: function MyService_Factory(t) { let r = null; if (t) {
r = new t();
}
else {
r = (() => new MyAlternateService())(i0.ɵɵinject(SomeDep));
} return r; }, providedIn: 'root' });
MyService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, providedIn: 'root', useFactory: () => new MyAlternateService(), deps: [{ token: SomeDep }] });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable,
args: [{ providedIn: 'root', useFactory: () => new MyAlternateService(), deps: [SomeDep] }]
@ -199,14 +194,14 @@ import * as i0 from "@angular/core";
class MyAlternateService {
}
MyAlternateService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyAlternateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
MyAlternateService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyAlternateService, factory: MyAlternateService.ɵfac });
MyAlternateService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyAlternateService });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyAlternateService, [{
type: Injectable
}], null, null); })();
export class MyService {
}
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
MyService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyService, factory: function (t) { return MyAlternateService.ɵfac(t); }, providedIn: 'root' });
MyService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, providedIn: 'root', useClass: MyAlternateService });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable,
args: [{ providedIn: 'root', useClass: MyAlternateService }]
@ -231,19 +226,14 @@ class SomeDep {
class MyAlternateService {
}
MyAlternateService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyAlternateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
MyAlternateService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyAlternateService, factory: MyAlternateService.ɵfac });
MyAlternateService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyAlternateService });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyAlternateService, [{
type: Injectable
}], null, null); })();
export class MyService {
}
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
MyService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyService, factory: function MyService_Factory(t) { let r = null; if (t) {
r = new t();
}
else {
r = new MyAlternateService(i0.ɵɵinject(SomeDep));
} return r; }, providedIn: 'root' });
MyService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, providedIn: 'root', useClass: MyAlternateService, deps: [{ token: SomeDep }] });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable,
args: [{ providedIn: 'root', useClass: MyAlternateService, deps: [SomeDep] }]
@ -266,7 +256,7 @@ import * as i0 from "@angular/core";
class SomeProvider {
}
SomeProvider.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
SomeProvider.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: SomeProvider, factory: function (t) { return SomeProviderImpl.ɵfac(t); }, providedIn: 'root' });
SomeProvider.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeProvider, providedIn: 'root', useClass: i0.forwardRef(function () { return SomeProviderImpl; }) });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SomeProvider, [{
type: Injectable,
args: [{ providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl) }]
@ -274,7 +264,7 @@ SomeProvider.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: SomeProvide
class SomeProviderImpl extends SomeProvider {
}
SomeProviderImpl.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeProviderImpl, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
SomeProviderImpl.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: SomeProviderImpl, factory: SomeProviderImpl.ɵfac });
SomeProviderImpl.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeProviderImpl });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SomeProviderImpl, [{
type: Injectable
}], null, null); })();
@ -284,6 +274,55 @@ SomeProviderImpl.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: SomePro
****************************************************************************************************/
export {};
/****************************************************************************************************
* PARTIAL FILE: providedin_forwardref.js
****************************************************************************************************/
import { forwardRef, Injectable, NgModule } from '@angular/core';
import * as i0 from "@angular/core";
export class Dep {
}
Dep.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Dep, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
Dep.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Dep });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Dep, [{
type: Injectable
}], null, null); })();
export class Service {
constructor(dep) { }
}
Service.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Service, deps: [{ token: Dep }], target: i0.ɵɵFactoryTarget.Injectable });
Service.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Service, providedIn: i0.forwardRef(function () { return Mod; }) });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Service, [{
type: Injectable,
args: [{ providedIn: forwardRef(() => Mod) }]
}], function () { return [{ type: Dep }]; }, null); })();
export class Mod {
}
Mod.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Mod, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
Mod.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Mod });
Mod.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Mod });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Mod, [{
type: NgModule
}], null, null); })();
/****************************************************************************************************
* PARTIAL FILE: providedin_forwardref.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class Dep {
static ɵfac: i0.ɵɵFactoryDeclaration<Dep, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<Dep>;
}
export declare class Service {
constructor(dep: Dep);
static ɵfac: i0.ɵɵFactoryDeclaration<Service, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<Service>;
}
export declare class Mod {
static ɵfac: i0.ɵɵFactoryDeclaration<Mod, never>;
static ɵmod: i0.ɵɵNgModuleDeclaration<Mod, never, never, never>;
static ɵinj: i0.ɵɵInjectorDeclaration<Mod>;
}
/****************************************************************************************************
* PARTIAL FILE: pipe_and_injectable.js
****************************************************************************************************/
@ -292,7 +331,7 @@ import * as i0 from "@angular/core";
class Service {
}
Service.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Service, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
Service.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: Service, factory: Service.ɵfac });
Service.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Service });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Service, [{
type: Injectable
}], null, null); })();
@ -304,7 +343,7 @@ export class MyPipe {
}
MyPipe.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyPipe, deps: [{ token: Service }], target: i0.ɵɵFactoryTarget.Pipe });
MyPipe.ɵpipe = i0.ɵɵngDeclarePipe({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyPipe, name: "myPipe" });
MyPipe.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyPipe, factory: MyPipe.ɵfac });
MyPipe.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyPipe });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyPipe, [{
type: Injectable
}, {
@ -319,7 +358,7 @@ export class MyOtherPipe {
}
MyOtherPipe.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyOtherPipe, deps: [{ token: Service }], target: i0.ɵɵFactoryTarget.Pipe });
MyOtherPipe.ɵpipe = i0.ɵɵngDeclarePipe({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyOtherPipe, name: "myOtherPipe" });
MyOtherPipe.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyOtherPipe, factory: MyOtherPipe.ɵfac });
MyOtherPipe.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyOtherPipe });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyOtherPipe, [{
type: Pipe,
args: [{ name: 'myOtherPipe' }]

View File

@ -137,6 +137,20 @@
}
]
},
{
"description": "should support forward refs in a providedIn clause",
"inputFiles": [
"providedin_forwardref.ts"
],
"expectations": [
{
"failureMessage": "Incorrect factory definition",
"files": [
"providedin_forwardref.js"
]
}
]
},
{
"description": "should have the pipe factory take precedence over the injectable factory, if a class has multiple decorators",
"inputFiles": [

View File

@ -0,0 +1,8 @@
Service.ɵfac = function Service_Factory(t) { return new (t || Service)($i0$.ɵɵinject(Dep)); };
Service.ɵprov = /*@__PURE__*/ $i0$.ɵɵdefineInjectable({ token: Service, factory: Service.ɵfac, providedIn: $i0$.forwardRef(function () { return Mod; }) });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && $i0$.ɵsetClassMetadata(Service, [{
type: Injectable,
args: [{ providedIn: forwardRef(() => Mod) }]
}], function () { return [{ type: Dep }]; }, null); })();
export class Mod {
}

View File

@ -0,0 +1,12 @@
import {forwardRef, Injectable, NgModule} from '@angular/core';
@Injectable()
export class Dep {
}
@Injectable({providedIn: forwardRef(() => Mod)})
export class Service {
constructor(dep: Dep) {}
}
@NgModule()
export class Mod {
}

View File

@ -208,11 +208,11 @@ function allTests(os: string) {
expect(jsContents)
.toContain(
'Service.ɵfac = function Service_Factory(t) { return new (t || Service)(i0.ɵɵinject(Dep)); };');
expect(jsContents).toContain('providedIn: forwardRef(function () { return Mod; }) })');
expect(jsContents).toContain('providedIn: i0.forwardRef(function () { return Mod; }) })');
expect(jsContents).not.toContain('__decorate');
const dtsContents = env.getContents('test.d.ts');
expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDef<Dep>;');
expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDef<Service>;');
expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDeclaration<Dep>;');
expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDeclaration<Service>;');
expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration<Dep, never>;');
expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration<Service, never>;');
expect(dtsContents).toContain('i0.ɵɵFactoryDeclaration<Mod, never>;');

View File

@ -36,6 +36,11 @@ export function identifierName(compileIdentifier: CompileIdentifierMetadata|null
if (ref['__anonymousType']) {
return ref['__anonymousType'];
}
if (ref['__forward_ref__']) {
// We do not want to try to stringify a `forwardRef()` function because that would cause the
// inner function to be evaluated too early, defeating the whole point of the `forwardRef`.
return '__forward_ref__';
}
let identifier = stringify(ref);
if (identifier.indexOf('(') >= 0) {
// case: anonymous functions!

View File

@ -107,6 +107,7 @@ export {compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBin
export {compileDeclareComponentFromMetadata} from './render3/partial/component';
export {compileDeclareDirectiveFromMetadata} from './render3/partial/directive';
export {compileDeclareFactoryFunction} from './render3/partial/factory';
export {compileDeclareInjectableFromMetadata} from './render3/partial/injectable';
export {compileDeclareInjectorFromMetadata} from './render3/partial/injector';
export {compileDeclareNgModuleFromMetadata} from './render3/partial/ng_module';
export {compileDeclarePipeFromMetadata} from './render3/partial/pipe';

View File

@ -33,6 +33,8 @@ export interface CompilerFacade {
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, declaration: R3DeclarePipeFacade): any;
compileInjectable(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3InjectableMetadataFacade): any;
compileInjectableDeclaration(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3DeclareInjectableFacade): any;
compileInjector(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3InjectorMetadataFacade): any;
compileInjectorDeclaration(
@ -80,7 +82,9 @@ export type StringMapWithRename = {
[key: string]: string|[string, string];
};
export type Provider = any;
export type Provider = unknown;
export type Type = Function;
export type OpaqueValue = unknown;
export enum FactoryTarget {
Directive = 0,
@ -91,7 +95,7 @@ export enum FactoryTarget {
}
export interface R3DependencyMetadataFacade {
token: unknown;
token: OpaqueValue;
attribute: string|null;
host: boolean;
optional: boolean;
@ -100,7 +104,7 @@ export interface R3DependencyMetadataFacade {
}
export interface R3DeclareDependencyMetadataFacade {
token: unknown;
token: OpaqueValue;
attribute?: boolean;
host?: boolean;
optional?: boolean;
@ -110,25 +114,25 @@ export interface R3DeclareDependencyMetadataFacade {
export interface R3PipeMetadataFacade {
name: string;
type: any;
type: Type;
pipeName: string;
pure: boolean;
}
export interface R3InjectableMetadataFacade {
name: string;
type: any;
type: Type;
typeArgumentCount: number;
providedIn: any;
useClass?: any;
useFactory?: any;
useExisting?: any;
useValue?: any;
userDeps?: R3DependencyMetadataFacade[];
providedIn?: Type|'root'|'platform'|'any'|null;
useClass?: OpaqueValue;
useFactory?: OpaqueValue;
useExisting?: OpaqueValue;
useValue?: OpaqueValue;
deps?: R3DependencyMetadataFacade[];
}
export interface R3NgModuleMetadataFacade {
type: any;
type: Type;
bootstrap: Function[];
declarations: Function[];
imports: Function[];
@ -139,19 +143,19 @@ export interface R3NgModuleMetadataFacade {
export interface R3InjectorMetadataFacade {
name: string;
type: any;
providers: any[];
imports: any[];
type: Type;
providers: Provider[];
imports: OpaqueValue[];
}
export interface R3DirectiveMetadataFacade {
name: string;
type: any;
type: Type;
typeSourceSpan: ParseSourceSpan;
selector: string|null;
queries: R3QueryMetadataFacade[];
host: {[key: string]: string};
propMetadata: {[key: string]: any[]};
propMetadata: {[key: string]: OpaqueValue[]};
lifecycle: {usesOnChanges: boolean;};
inputs: string[];
outputs: string[];
@ -164,7 +168,7 @@ export interface R3DirectiveMetadataFacade {
export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
template: string;
preserveWhitespaces: boolean;
animations: any[]|undefined;
animations: OpaqueValue[]|undefined;
pipes: Map<string, any>;
directives: R3UsedDirectiveMetadata[];
styles: string[];
@ -174,11 +178,9 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
changeDetection?: ChangeDetectionStrategy;
}
export type OpaqueValue = unknown;
export interface R3DeclareDirectiveFacade {
selector?: string;
type: Function;
type: Type;
inputs?: {[classPropertyName: string]: string|[string, string]};
outputs?: {[classPropertyName: string]: string};
host?: {
@ -229,18 +231,28 @@ export interface R3UsedDirectiveMetadata {
export interface R3FactoryDefMetadataFacade {
name: string;
type: any;
type: Type;
typeArgumentCount: number;
deps: R3DependencyMetadataFacade[]|null;
target: FactoryTarget;
}
export interface R3DeclareFactoryFacade {
type: Function;
type: Type;
deps: R3DeclareDependencyMetadataFacade[]|null;
target: FactoryTarget;
}
export interface R3DeclareInjectableFacade {
type: Type;
providedIn?: Type|'root'|'platform'|'any'|null;
useClass?: OpaqueValue;
useFactory?: OpaqueValue;
useExisting?: OpaqueValue;
useValue?: OpaqueValue;
deps?: R3DeclareDependencyMetadataFacade[];
}
export enum ViewEncapsulation {
Emulated = 0,
// Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
@ -253,10 +265,10 @@ export type ChangeDetectionStrategy = number;
export interface R3QueryMetadataFacade {
propertyName: string;
first: boolean;
predicate: any|string[];
predicate: OpaqueValue|string[];
descendants: boolean;
emitDistinctChangesOnly: boolean;
read: any|null;
read: OpaqueValue|null;
static: boolean;
}
@ -271,13 +283,13 @@ export interface R3DeclareQueryMetadataFacade {
}
export interface R3DeclareInjectorFacade {
type: Function;
type: Type;
imports?: OpaqueValue[];
providers?: OpaqueValue[];
}
export interface R3DeclareNgModuleFacade {
type: Function;
type: Type;
bootstrap?: OpaqueValue[]|(() => OpaqueValue[]);
declarations?: OpaqueValue[]|(() => OpaqueValue[]);
imports?: OpaqueValue[]|(() => OpaqueValue[]);
@ -287,7 +299,7 @@ export interface R3DeclareNgModuleFacade {
}
export interface R3DeclarePipeFacade {
type: Function;
type: Type;
name: string;
pure?: boolean;
}

View File

@ -66,9 +66,6 @@ export class Identifiers {
static directiveInject: o.ExternalReference = {name: 'ɵɵdirectiveInject', moduleName: CORE};
static INJECTOR: o.ExternalReference = {name: 'INJECTOR', moduleName: CORE};
static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE};
static ɵɵdefineInjectable: o.ExternalReference = {name: 'ɵɵdefineInjectable', moduleName: CORE};
static InjectableDeclaration:
o.ExternalReference = {name: 'ɵɵInjectableDeclaration', moduleName: CORE};
static ViewEncapsulation: o.ExternalReference = {
name: 'ViewEncapsulation',
moduleName: CORE,

View File

@ -7,16 +7,14 @@
*/
import {StaticSymbol} from './aot/static_symbol';
import {CompileInjectableMetadata, CompileNgModuleMetadata, CompileProviderMetadata, identifierName} from './compile_metadata';
import {CompileInjectableMetadata, identifierName} from './compile_metadata';
import {CompileReflector} from './compile_reflector';
import {InjectFlags, NodeFlags} from './core';
import {InjectFlags} from './core';
import {Identifiers} from './identifiers';
import * as o from './output/output_ast';
import {convertValueToOutputAst} from './output/value_util';
import {typeSourceSpan} from './parse_util';
import {NgModuleProviderAnalyzer} from './provider_analyzer';
import {Identifiers as R3} from './render3/r3_identifiers';
import {OutputContext} from './util';
import {componentFactoryResolverProviderDef, depDef, providerDef} from './view_compiler/provider_compiler';
type MapEntry = {
key: string,
@ -116,8 +114,7 @@ export class InjectableCompiler {
mapEntry('token', ctx.importExpr(injectable.type.reference)),
mapEntry('providedIn', providedIn),
];
return o.importExpr(Identifiers.ɵɵdefineInjectable)
.callFn([o.literalMap(def)], undefined, true);
return o.importExpr(R3.ɵɵdefineInjectable).callFn([o.literalMap(def)], undefined, true);
}
compile(injectable: CompileInjectableMetadata, ctx: OutputContext): void {

View File

@ -6,32 +6,62 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Identifiers} from './identifiers';
import * as o from './output/output_ast';
import {generateForwardRef} from './render3/partial/util';
import {compileFactoryFunction, FactoryTarget, R3DependencyMetadata, R3FactoryDelegateType, R3FactoryMetadata} from './render3/r3_factory';
import {R3Reference, typeWithParameters} from './render3/util';
import {Identifiers} from './render3/r3_identifiers';
import {R3CompiledExpression, R3Reference, typeWithParameters} from './render3/util';
import {DefinitionMap} from './render3/view/util';
export interface InjectableDef {
expression: o.Expression;
type: o.Type;
statements: o.Statement[];
}
export interface R3InjectableMetadata {
name: string;
type: R3Reference;
internalType: o.Expression;
typeArgumentCount: number;
providedIn: o.Expression;
useClass?: o.Expression;
providedIn: R3ProviderExpression;
useClass?: R3ProviderExpression;
useFactory?: o.Expression;
useExisting?: o.Expression;
useValue?: o.Expression;
userDeps?: R3DependencyMetadata[];
useExisting?: R3ProviderExpression;
useValue?: R3ProviderExpression;
deps?: R3DependencyMetadata[];
}
export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
/**
* An expression used when instantiating an injectable.
*
* This is the type of the `useClass`, `useExisting` and `useValue` properties of
* `R3InjectableMetadata` since those can refer to types that may eagerly reference types that have
* not yet been defined.
*/
export interface R3ProviderExpression<T extends o.Expression = o.Expression> {
/**
* The expression that is used to instantiate the Injectable.
*/
expression: T;
/**
* If true, then the `expression` contains a reference to something that has not yet been
* defined.
*
* 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.
*
* In some cases the expression will naturally be placed inside such a function closure, such as
* in a fully compiled factory function. In those case nothing more needs to be done.
*
* 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;
}
export function createR3ProviderExpression<T extends o.Expression>(
expression: T, isForwardRef: boolean): R3ProviderExpression<T> {
return {expression, isForwardRef};
}
export function compileInjectable(
meta: R3InjectableMetadata, resolveForwardRefs: boolean): R3CompiledExpression {
let result: {expression: o.Expression, statements: o.Statement[]}|null = null;
const factoryMeta: R3FactoryMetadata = {
@ -51,32 +81,36 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
// A special case exists for useClass: Type where Type is the injectable type itself and no
// deps are specified, in which case 'useClass' is effectively ignored.
const useClassOnSelf = meta.useClass.isEquivalent(meta.internalType);
const useClassOnSelf = meta.useClass.expression.isEquivalent(meta.internalType);
let deps: R3DependencyMetadata[]|undefined = undefined;
if (meta.userDeps !== undefined) {
deps = meta.userDeps;
if (meta.deps !== undefined) {
deps = meta.deps;
}
if (deps !== undefined) {
// factory: () => new meta.useClass(...deps)
result = compileFactoryFunction({
...factoryMeta,
delegate: meta.useClass,
delegate: meta.useClass.expression,
delegateDeps: deps,
delegateType: R3FactoryDelegateType.Class,
});
} else if (useClassOnSelf) {
result = compileFactoryFunction(factoryMeta);
} else {
result = delegateToFactory(
meta.type.value as o.WrappedNodeExpr<any>, meta.useClass as o.WrappedNodeExpr<any>);
result = {
statements: [],
expression: delegateToFactory(
meta.type.value as o.WrappedNodeExpr<any>,
meta.useClass.expression as o.WrappedNodeExpr<any>, resolveForwardRefs)
};
}
} else if (meta.useFactory !== undefined) {
if (meta.userDeps !== undefined) {
if (meta.deps !== undefined) {
result = compileFactoryFunction({
...factoryMeta,
delegate: meta.useFactory,
delegateDeps: meta.userDeps || [],
delegateDeps: meta.deps || [],
delegateType: R3FactoryDelegateType.Function,
});
} else {
@ -91,17 +125,21 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
// value is undefined.
result = compileFactoryFunction({
...factoryMeta,
expression: meta.useValue,
expression: meta.useValue.expression,
});
} else if (meta.useExisting !== undefined) {
// useExisting is an `inject` call on the existing token.
result = compileFactoryFunction({
...factoryMeta,
expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting]),
expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting.expression]),
});
} else {
result = delegateToFactory(
meta.type.value as o.WrappedNodeExpr<any>, meta.internalType as o.WrappedNodeExpr<any>);
result = {
statements: [],
expression: delegateToFactory(
meta.type.value as o.WrappedNodeExpr<any>, meta.internalType as o.WrappedNodeExpr<any>,
resolveForwardRefs)
};
}
const token = meta.internalType;
@ -112,32 +150,59 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
injectableProps.set('factory', result.expression);
// Only generate providedIn property if it has a non-null value
if ((meta.providedIn as o.LiteralExpr).value !== null) {
injectableProps.set('providedIn', meta.providedIn);
if ((meta.providedIn.expression as o.LiteralExpr).value !== null) {
injectableProps.set(
'providedIn',
meta.providedIn.isForwardRef ? generateForwardRef(meta.providedIn.expression) :
meta.providedIn.expression);
}
const expression = o.importExpr(Identifiers.ɵɵdefineInjectable)
.callFn([injectableProps.toLiteralMap()], undefined, true);
const type = new o.ExpressionType(o.importExpr(
Identifiers.InjectableDeclaration,
[typeWithParameters(meta.type.type, meta.typeArgumentCount)]));
return {
expression,
type,
type: createInjectableType(meta),
statements: result.statements,
};
}
function delegateToFactory(type: o.WrappedNodeExpr<any>, internalType: o.WrappedNodeExpr<any>) {
return {
statements: [],
// If types are the same, we can generate `factory: type.ɵfac`
// If types are different, we have to generate a wrapper function to ensure
// the internal type has been resolved (`factory: function(t) { return type.ɵfac(t); }`)
expression: type.node === internalType.node ?
internalType.prop('ɵfac') :
o.fn([new o.FnParam('t', o.DYNAMIC_TYPE)], [new o.ReturnStatement(internalType.callMethod(
'ɵfac', [o.variable('t')]))])
};
export function createInjectableType(meta: R3InjectableMetadata) {
return new o.ExpressionType(o.importExpr(
Identifiers.InjectableDeclaration,
[typeWithParameters(meta.type.type, meta.typeArgumentCount)]));
}
function delegateToFactory(
type: o.WrappedNodeExpr<any>, internalType: o.WrappedNodeExpr<any>,
unwrapForwardRefs: boolean): o.Expression {
if (type.node === internalType.node) {
// The types are the same, so we can simply delegate directly to the type's factory.
// ```
// factory: type.ɵfac
// ```
return internalType.prop('ɵfac');
}
if (!unwrapForwardRefs) {
// The type is not wrapped in a `forwardRef()`, so we create a simple factory function that
// accepts a sub-type as an argument.
// ```
// factory: function(t) { return internalType.ɵfac(t); }
// ```
return createFactoryFunction(internalType);
}
// The internalType is actually wrapped in a `forwardRef()` so we need to resolve that before
// calling its factory.
// ```
// factory: function(t) { return core.resolveForwardRef(type).ɵfac(t); }
// ```
const unwrappedType = o.importExpr(Identifiers.resolveForwardRef).callFn([internalType]);
return createFactoryFunction(unwrappedType);
}
function createFactoryFunction(type: o.Expression): o.FunctionExpr {
return o.fn(
[new o.FnParam('t', o.DYNAMIC_TYPE)],
[new o.ReturnStatement(type.callMethod('ɵfac', [o.variable('t')]))]);
}

View File

@ -7,10 +7,10 @@
*/
import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, OpaqueValue, R3ComponentMetadataFacade, R3DeclareComponentFacade, R3DeclareDependencyMetadataFacade, R3DeclareDirectiveFacade, R3DeclareFactoryFacade, R3DeclareInjectorFacade, R3DeclareNgModuleFacade, R3DeclarePipeFacade, R3DeclareQueryMetadataFacade, R3DeclareUsedDirectiveFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3FactoryDefMetadataFacade, R3InjectableMetadataFacade, R3InjectorMetadataFacade, R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, StringMap, StringMapWithRename} from './compiler_facade_interface';
import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, OpaqueValue, R3ComponentMetadataFacade, R3DeclareComponentFacade, R3DeclareDependencyMetadataFacade, R3DeclareDirectiveFacade, R3DeclareFactoryFacade, R3DeclareInjectableFacade, R3DeclareInjectorFacade, R3DeclareNgModuleFacade, R3DeclarePipeFacade, R3DeclareQueryMetadataFacade, R3DeclareUsedDirectiveFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3FactoryDefMetadataFacade, R3InjectableMetadataFacade, R3InjectorMetadataFacade, R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, StringMap, StringMapWithRename} from './compiler_facade_interface';
import {ConstantPool} from './constant_pool';
import {ChangeDetectionStrategy, HostBinding, HostListener, Input, Output, Type, ViewEncapsulation} from './core';
import {compileInjectable} from './injectable_compiler_2';
import {ChangeDetectionStrategy, HostBinding, HostListener, Input, Output, ViewEncapsulation} from './core';
import {compileInjectable, createR3ProviderExpression, R3ProviderExpression} from './injectable_compiler_2';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/interpolation_config';
import {DeclareVarStmt, Expression, literal, LiteralExpr, Statement, StmtModifier, WrappedNodeExpr} from './output/output_ast';
import {JitEvaluator} from './output/output_jit';
@ -60,18 +60,41 @@ export class CompilerFacadeImpl implements CompilerFacade {
compileInjectable(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
facade: R3InjectableMetadataFacade): any {
const {expression, statements} = compileInjectable({
name: facade.name,
type: wrapReference(facade.type),
internalType: new WrappedNodeExpr(facade.type),
typeArgumentCount: facade.typeArgumentCount,
providedIn: computeProvidedIn(facade.providedIn),
useClass: wrapExpression(facade, USE_CLASS),
useFactory: wrapExpression(facade, USE_FACTORY),
useValue: wrapExpression(facade, USE_VALUE),
useExisting: wrapExpression(facade, USE_EXISTING),
userDeps: convertR3DependencyMetadataArray(facade.userDeps) || undefined,
});
const {expression, statements} = compileInjectable(
{
name: facade.name,
type: wrapReference(facade.type),
internalType: new WrappedNodeExpr(facade.type),
typeArgumentCount: facade.typeArgumentCount,
providedIn: computeProvidedIn(facade.providedIn),
useClass: convertToProviderExpression(facade, USE_CLASS),
useFactory: wrapExpression(facade, USE_FACTORY),
useValue: convertToProviderExpression(facade, USE_VALUE),
useExisting: convertToProviderExpression(facade, USE_EXISTING),
deps: facade.deps?.map(convertR3DependencyMetadata),
},
/* resolveForwardRefs */ true);
return this.jitExpression(expression, angularCoreEnv, sourceMapUrl, statements);
}
compileInjectableDeclaration(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
facade: R3DeclareInjectableFacade): any {
const {expression, statements} = compileInjectable(
{
name: facade.type.name,
type: wrapReference(facade.type),
internalType: new WrappedNodeExpr(facade.type),
typeArgumentCount: 0,
providedIn: computeProvidedIn(facade.providedIn),
useClass: convertToProviderExpression(facade, USE_CLASS),
useFactory: wrapExpression(facade, USE_FACTORY),
useValue: convertToProviderExpression(facade, USE_VALUE),
useExisting: convertToProviderExpression(facade, USE_EXISTING),
deps: facade.deps?.map(convertR3DeclareDependencyMetadata),
},
/* resolveForwardRefs */ true);
return this.jitExpression(expression, angularCoreEnv, sourceMapUrl, statements);
}
@ -446,6 +469,22 @@ function parseJitTemplate(
type R3DirectiveMetadataFacadeNoPropAndWhitespace =
Pick<R3DirectiveMetadataFacade, Exclude<keyof R3DirectiveMetadataFacade, 'propMetadata'>>;
/**
* Convert the expression, if present to an `R3ProviderExpression`.
*
* 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`.
*/
function convertToProviderExpression(obj: any, property: string): R3ProviderExpression|undefined {
if (obj.hasOwnProperty(property)) {
return createR3ProviderExpression(new WrappedNodeExpr(obj[property]), /* isForwardRef */ false);
} else {
return undefined;
}
}
function wrapExpression(obj: any, property: string): WrappedNodeExpr<any>|undefined {
if (obj.hasOwnProperty(property)) {
return new WrappedNodeExpr(obj[property]);
@ -454,12 +493,12 @@ function wrapExpression(obj: any, property: string): WrappedNodeExpr<any>|undefi
}
}
function computeProvidedIn(providedIn: Type|string|null|undefined): Expression {
if (providedIn == null || typeof providedIn === 'string') {
return new LiteralExpr(providedIn);
} else {
return new WrappedNodeExpr(providedIn);
}
function computeProvidedIn(providedIn: Function|string|null|undefined): R3ProviderExpression {
const expression = (providedIn == null || typeof providedIn === 'string') ?
new LiteralExpr(providedIn ?? null) :
new WrappedNodeExpr(providedIn);
// See `convertToProviderExpression()` for why `isForwardRef` is false.
return createR3ProviderExpression(expression, /* isForwardRef */ false);
}
function convertR3DependencyMetadataArray(facades: R3DependencyMetadataFacade[]|null|

View File

@ -367,6 +367,53 @@ export enum FactoryTarget {
NgModule = 4,
}
/**
* Describes the shape of the object that the `ɵɵngDeclareInjectable()` function accepts.
*
* This interface serves primarily as documentation, as conformance to this interface is not
* enforced during linking.
*/
export interface R3DeclareInjectableMetadata extends R3PartialDeclaration {
/**
* If provided, specifies that the declared injectable belongs to a particular injector:
* - `InjectorType` such as `NgModule`,
* - `'root'` the root injector
* - `'any'` all injectors.
* If not provided, then it does not belong to any injector. Must be explicitly listed in the
* providers of an injector.
*/
providedIn?: o.Expression;
/**
* If provided, an expression that evaluates to a class to use when creating an instance of this
* injectable.
*/
useClass?: o.Expression;
/**
* If provided, an expression that evaluates to a function to use when creating an instance of
* this injectable.
*/
useFactory?: o.Expression;
/**
* If provided, an expression that evaluates to a token of another injectable that this injectable
* aliases.
*/
useExisting?: o.Expression;
/**
* If provided, an expression that evaluates to the value of the instance of this injectable.
*/
useValue?: o.Expression;
/**
* An array of dependencies to support instantiating this injectable via `useClass` or
* `useFactory`.
*/
deps?: R3DeclareDependencyMetadata[];
}
/**
* Metadata indicating how a dependency should be injected into a factory.
*/

View File

@ -18,7 +18,7 @@ import {DefinitionMap} from '../view/util';
import {R3DeclareComponentMetadata, R3DeclareUsedDirectiveMetadata} from './api';
import {createDirectiveDefinitionMap} from './directive';
import {toOptionalLiteralArray} from './util';
import {generateForwardRef, toOptionalLiteralArray} from './util';
/**
@ -163,7 +163,3 @@ function compileUsedPipeMetadata(meta: R3ComponentMetadata): o.LiteralMapExpr|nu
}
return o.literalMap(entries);
}
function generateForwardRef(expr: o.Expression): o.Expression {
return o.importExpr(R3.forwardRef).callFn([o.fn([], [new o.ReturnStatement(expr)])]);
}

View File

@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as o from '../../output/output_ast';
import {createFactoryType, FactoryTarget, R3DependencyMetadata, R3FactoryMetadata} from '../r3_factory';
import {createFactoryType, FactoryTarget, R3FactoryMetadata} from '../r3_factory';
import {Identifiers as R3} from '../r3_identifiers';
import {R3CompiledExpression} from '../util';
import {DefinitionMap} from '../view/util';
import {R3DeclareDependencyMetadata, R3DeclareFactoryMetadata} from './api';
import {R3DeclareFactoryMetadata} from './api';
import {compileDependencies} from './util';
export function compileDeclareFactoryFunction(meta: R3FactoryMetadata): R3CompiledExpression {
const definitionMap = new DefinitionMap<R3DeclareFactoryMetadata>();
@ -27,35 +28,3 @@ export function compileDeclareFactoryFunction(meta: R3FactoryMetadata): R3Compil
type: createFactoryType(meta),
};
}
function compileDependencies(deps: R3DependencyMetadata[]|'invalid'|null): o.LiteralExpr|
o.LiteralArrayExpr {
if (deps === 'invalid') {
return o.literal('invalid');
} else if (deps === null) {
return o.literal(null);
} else {
return o.literalArr(deps.map(compileDependency));
}
}
function compileDependency(dep: R3DependencyMetadata): o.LiteralMapExpr {
const depMeta = new DefinitionMap<R3DeclareDependencyMetadata>();
depMeta.set('token', dep.token);
if (dep.attributeNameType !== null) {
depMeta.set('attribute', o.literal(true));
}
if (dep.host) {
depMeta.set('host', o.literal(true));
}
if (dep.optional) {
depMeta.set('optional', o.literal(true));
}
if (dep.self) {
depMeta.set('self', o.literal(true));
}
if (dep.skipSelf) {
depMeta.set('skipSelf', o.literal(true));
}
return depMeta.toLiteralMap();
}

View File

@ -0,0 +1,93 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* 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 {createInjectableType, R3InjectableMetadata, R3ProviderExpression} from '../../injectable_compiler_2';
import * as o from '../../output/output_ast';
import {Identifiers as R3} from '../r3_identifiers';
import {R3CompiledExpression} from '../util';
import {DefinitionMap} from '../view/util';
import {R3DeclareInjectableMetadata} from './api';
import {compileDependency, generateForwardRef} from './util';
/**
* Compile a Injectable declaration defined by the `R3InjectableMetadata`.
*/
export function compileDeclareInjectableFromMetadata(meta: R3InjectableMetadata):
R3CompiledExpression {
const definitionMap = createInjectableDefinitionMap(meta);
const expression = o.importExpr(R3.declareInjectable).callFn([definitionMap.toLiteralMap()]);
const type = createInjectableType(meta);
return {expression, type, statements: []};
}
/**
* Gathers the declaration fields for a Injectable into a `DefinitionMap`.
*/
export function createInjectableDefinitionMap(meta: R3InjectableMetadata):
DefinitionMap<R3DeclareInjectableMetadata> {
const definitionMap = new DefinitionMap<R3DeclareInjectableMetadata>();
definitionMap.set('version', o.literal('0.0.0-PLACEHOLDER'));
definitionMap.set('ngImport', o.importExpr(R3.core));
definitionMap.set('type', meta.internalType);
// Only generate providedIn property if it has a non-null value
if (meta.providedIn !== undefined) {
const providedIn = convertFromProviderExpression(meta.providedIn);
if ((providedIn as o.LiteralExpr).value !== null) {
definitionMap.set('providedIn', providedIn);
}
}
if (meta.useClass !== undefined) {
definitionMap.set('useClass', convertFromProviderExpression(meta.useClass));
}
if (meta.useExisting !== undefined) {
definitionMap.set('useExisting', convertFromProviderExpression(meta.useExisting));
}
if (meta.useValue !== undefined) {
definitionMap.set('useValue', convertFromProviderExpression(meta.useValue));
}
// Factories do not contain `ForwardRef`s since any types are already wrapped in a function call
// so the types will not be eagerly evaluated. Therefore we do not need to process this expression
// with `convertFromProviderExpression()`.
if (meta.useFactory !== undefined) {
definitionMap.set('useFactory', meta.useFactory);
}
if (meta.deps !== undefined) {
definitionMap.set('deps', o.literalArr(meta.deps.map(compileDependency)));
}
return definitionMap;
}
/**
* Convert an `R3ProviderExpression` to an `Expression`, possibly wrapping its expression in a
* `forwardRef()` call.
*
* If `R3ProviderExpression.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.
*
* See `packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts` and
* `packages/compiler/src/jit_compiler_facade.ts` for more information.
*/
function convertFromProviderExpression({expression, isForwardRef}: R3ProviderExpression):
o.Expression {
return isForwardRef ? generateForwardRef(expression) : expression;
}

View File

@ -22,6 +22,9 @@ export function compileDeclareInjectorFromMetadata(meta: R3InjectorMetadata): R3
return {expression, type, statements: []};
}
/**
* Gathers the declaration fields for an Injector into a `DefinitionMap`.
*/
function createInjectorDefinitionMap(meta: R3InjectorMetadata):
DefinitionMap<R3DeclareInjectorMetadata> {
const definitionMap = new DefinitionMap<R3DeclareInjectorMetadata>();

View File

@ -23,6 +23,9 @@ export function compileDeclareNgModuleFromMetadata(meta: R3NgModuleMetadata): R3
return {expression, type, statements: []};
}
/**
* Gathers the declaration fields for an NgModule into a `DefinitionMap`.
*/
function createNgModuleDefinitionMap(meta: R3NgModuleMetadata):
DefinitionMap<R3DeclareNgModuleMetadata> {
const definitionMap = new DefinitionMap<R3DeclareNgModuleMetadata>();

View File

@ -26,8 +26,7 @@ export function compileDeclarePipeFromMetadata(meta: R3PipeMetadata): R3Compiled
}
/**
* Gathers the declaration fields for a Pipe into a `DefinitionMap`. This allows for reusing
* this logic for components, as they extend the Pipe metadata.
* Gathers the declaration fields for a Pipe into a `DefinitionMap`.
*/
export function createPipeDefinitionMap(meta: R3PipeMetadata):
DefinitionMap<R3DeclarePipeMetadata> {

View File

@ -6,6 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as o from '../../output/output_ast';
import {R3DependencyMetadata} from '../r3_factory';
import {Identifiers} from '../r3_identifiers';
import {DefinitionMap} from '../view/util';
import {R3DeclareDependencyMetadata} from './api';
/**
* Creates an array literal expression from the given array, mapping all values to an expression
@ -46,3 +50,48 @@ export function toOptionalLiteralMap<T>(
return null;
}
}
export function compileDependencies(deps: R3DependencyMetadata[]|'invalid'|null): o.LiteralExpr|
o.LiteralArrayExpr {
if (deps === 'invalid') {
// The `deps` can be set to the string "invalid" by the `unwrapConstructorDependencies()`
// function, which tries to convert `ConstructorDeps` into `R3DependencyMetadata[]`.
return o.literal('invalid');
} else if (deps === null) {
return o.literal(null);
} else {
return o.literalArr(deps.map(compileDependency));
}
}
export function compileDependency(dep: R3DependencyMetadata): o.LiteralMapExpr {
const depMeta = new DefinitionMap<R3DeclareDependencyMetadata>();
depMeta.set('token', dep.token);
if (dep.attributeNameType !== null) {
depMeta.set('attribute', o.literal(true));
}
if (dep.host) {
depMeta.set('host', o.literal(true));
}
if (dep.optional) {
depMeta.set('optional', o.literal(true));
}
if (dep.self) {
depMeta.set('self', o.literal(true));
}
if (dep.skipSelf) {
depMeta.set('skipSelf', o.literal(true));
}
return depMeta.toLiteralMap();
}
/**
* Generate an expression that has the given `expr` wrapped in the following form:
*
* ```
* forwardRef(() => expr)
* ```
*/
export function generateForwardRef(expr: o.Expression): o.Expression {
return o.importExpr(Identifiers.forwardRef).callFn([o.fn([], [new o.ReturnStatement(expr)])]);
}

View File

@ -229,6 +229,11 @@ export class Identifiers {
static forwardRef: o.ExternalReference = {name: 'forwardRef', moduleName: CORE};
static resolveForwardRef: o.ExternalReference = {name: 'resolveForwardRef', moduleName: CORE};
static ɵɵdefineInjectable: o.ExternalReference = {name: 'ɵɵdefineInjectable', moduleName: CORE};
static declareInjectable: o.ExternalReference = {name: 'ɵɵngDeclareInjectable', moduleName: CORE};
static InjectableDeclaration:
o.ExternalReference = {name: 'ɵɵInjectableDeclaration', moduleName: CORE};
static resolveWindow: o.ExternalReference = {name: 'ɵɵresolveWindow', moduleName: CORE};
static resolveDocument: o.ExternalReference = {name: 'ɵɵresolveDocument', moduleName: CORE};
static resolveBody: o.ExternalReference = {name: 'ɵɵresolveBody', moduleName: CORE};

View File

@ -76,6 +76,11 @@ const coreR3InjectableMetadataFacade: core.R3InjectableMetadataFacade =
const compilerR3InjectableMetadataFacade: compiler.R3InjectableMetadataFacade =
null! as core.R3InjectableMetadataFacade;
const coreR3DeclareInjectableFacade: core.R3DeclareInjectableFacade =
null! as compiler.R3DeclareInjectableFacade;
const compilerR3DeclareInjectableFacade: compiler.R3DeclareInjectableFacade =
null! as core.R3DeclareInjectableFacade;
const coreR3NgModuleMetadataFacade: core.R3NgModuleMetadataFacade =
null! as compiler.R3NgModuleMetadataFacade;
const compilerR3NgModuleMetadataFacade: compiler.R3NgModuleMetadataFacade =

View File

@ -33,6 +33,8 @@ export interface CompilerFacade {
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, declaration: R3DeclarePipeFacade): any;
compileInjectable(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3InjectableMetadataFacade): any;
compileInjectableDeclaration(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3DeclareInjectableFacade): any;
compileInjector(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3InjectorMetadataFacade): any;
compileInjectorDeclaration(
@ -80,7 +82,9 @@ export type StringMapWithRename = {
[key: string]: string|[string, string];
};
export type Provider = any;
export type Provider = unknown;
export type Type = Function;
export type OpaqueValue = unknown;
export enum FactoryTarget {
Directive = 0,
@ -91,7 +95,7 @@ export enum FactoryTarget {
}
export interface R3DependencyMetadataFacade {
token: unknown;
token: OpaqueValue;
attribute: string|null;
host: boolean;
optional: boolean;
@ -100,7 +104,7 @@ export interface R3DependencyMetadataFacade {
}
export interface R3DeclareDependencyMetadataFacade {
token: unknown;
token: OpaqueValue;
attribute?: boolean;
host?: boolean;
optional?: boolean;
@ -110,25 +114,25 @@ export interface R3DeclareDependencyMetadataFacade {
export interface R3PipeMetadataFacade {
name: string;
type: any;
type: Type;
pipeName: string;
pure: boolean;
}
export interface R3InjectableMetadataFacade {
name: string;
type: any;
type: Type;
typeArgumentCount: number;
providedIn: any;
useClass?: any;
useFactory?: any;
useExisting?: any;
useValue?: any;
userDeps?: R3DependencyMetadataFacade[];
providedIn?: Type|'root'|'platform'|'any'|null;
useClass?: OpaqueValue;
useFactory?: OpaqueValue;
useExisting?: OpaqueValue;
useValue?: OpaqueValue;
deps?: R3DependencyMetadataFacade[];
}
export interface R3NgModuleMetadataFacade {
type: any;
type: Type;
bootstrap: Function[];
declarations: Function[];
imports: Function[];
@ -139,19 +143,19 @@ export interface R3NgModuleMetadataFacade {
export interface R3InjectorMetadataFacade {
name: string;
type: any;
providers: any[];
imports: any[];
type: Type;
providers: Provider[];
imports: OpaqueValue[];
}
export interface R3DirectiveMetadataFacade {
name: string;
type: any;
type: Type;
typeSourceSpan: ParseSourceSpan;
selector: string|null;
queries: R3QueryMetadataFacade[];
host: {[key: string]: string};
propMetadata: {[key: string]: any[]};
propMetadata: {[key: string]: OpaqueValue[]};
lifecycle: {usesOnChanges: boolean;};
inputs: string[];
outputs: string[];
@ -164,7 +168,7 @@ export interface R3DirectiveMetadataFacade {
export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
template: string;
preserveWhitespaces: boolean;
animations: any[]|undefined;
animations: OpaqueValue[]|undefined;
pipes: Map<string, any>;
directives: R3UsedDirectiveMetadata[];
styles: string[];
@ -174,11 +178,9 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
changeDetection?: ChangeDetectionStrategy;
}
export type OpaqueValue = unknown;
export interface R3DeclareDirectiveFacade {
selector?: string;
type: Function;
type: Type;
inputs?: {[classPropertyName: string]: string|[string, string]};
outputs?: {[classPropertyName: string]: string};
host?: {
@ -229,18 +231,28 @@ export interface R3UsedDirectiveMetadata {
export interface R3FactoryDefMetadataFacade {
name: string;
type: any;
type: Type;
typeArgumentCount: number;
deps: R3DependencyMetadataFacade[]|null;
target: FactoryTarget;
}
export interface R3DeclareFactoryFacade {
type: Function;
type: Type;
deps: R3DeclareDependencyMetadataFacade[]|null;
target: FactoryTarget;
}
export interface R3DeclareInjectableFacade {
type: Type;
providedIn?: Type|'root'|'platform'|'any'|null;
useClass?: OpaqueValue;
useFactory?: OpaqueValue;
useExisting?: OpaqueValue;
useValue?: OpaqueValue;
deps?: R3DeclareDependencyMetadataFacade[];
}
export enum ViewEncapsulation {
Emulated = 0,
// Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
@ -253,10 +265,10 @@ export type ChangeDetectionStrategy = number;
export interface R3QueryMetadataFacade {
propertyName: string;
first: boolean;
predicate: any|string[];
predicate: OpaqueValue|string[];
descendants: boolean;
emitDistinctChangesOnly: boolean;
read: any|null;
read: OpaqueValue|null;
static: boolean;
}
@ -271,13 +283,13 @@ export interface R3DeclareQueryMetadataFacade {
}
export interface R3DeclareInjectorFacade {
type: Function;
type: Type;
imports?: OpaqueValue[];
providers?: OpaqueValue[];
}
export interface R3DeclareNgModuleFacade {
type: Function;
type: Type;
bootstrap?: OpaqueValue[]|(() => OpaqueValue[]);
declarations?: OpaqueValue[]|(() => OpaqueValue[]);
imports?: OpaqueValue[]|(() => OpaqueValue[]);
@ -287,7 +299,7 @@ export interface R3DeclareNgModuleFacade {
}
export interface R3DeclarePipeFacade {
type: Function;
type: Type;
name: string;
pure?: boolean;
}

View File

@ -272,6 +272,7 @@ export {
ɵɵngDeclareComponent,
ɵɵngDeclareDirective,
ɵɵngDeclareFactory,
ɵɵngDeclareInjectable,
ɵɵngDeclareInjector,
ɵɵngDeclareNgModule,
ɵɵngDeclarePipe,

View File

@ -5,6 +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 {resolveForwardRef} from '../forward_ref';
import {ɵɵinject, ɵɵinvalidFactoryDep} from '../injector_compatibility';
import {ɵɵdefineInjectable, ɵɵdefineInjector} from '../interface/defs';
@ -18,4 +19,5 @@ export const angularCoreDiEnv: {[name: string]: Function} = {
'ɵɵdefineInjector': ɵɵdefineInjector,
'ɵɵinject': ɵɵinject,
'ɵɵinvalidFactoryDep': ɵɵinvalidFactoryDep,
'resolveForwardRef': resolveForwardRef,
};

View File

@ -24,7 +24,7 @@ import {convertDependencies, reflectDependencies} from './util';
* Compile an Angular injectable according to its `Injectable` metadata, and patch the resulting
* injectable def (`ɵprov`) onto the injectable type.
*/
export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
export function compileInjectable(type: Type<any>, meta?: Injectable): void {
let ngInjectableDef: any = null;
let ngFactoryDef: any = null;
@ -34,8 +34,7 @@ export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
get: () => {
if (ngInjectableDef === null) {
ngInjectableDef = getCompilerFacade().compileInjectable(
angularCoreDiEnv, `ng:///${type.name}/ɵprov.js`,
getInjectableMetadata(type, srcMeta));
angularCoreDiEnv, `ng:///${type.name}/ɵprov.js`, getInjectableMetadata(type, meta));
}
return ngInjectableDef;
},
@ -47,12 +46,11 @@ export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
Object.defineProperty(type, NG_FACTORY_DEF, {
get: () => {
if (ngFactoryDef === null) {
const metadata = getInjectableMetadata(type, srcMeta);
const compiler = getCompilerFacade();
ngFactoryDef = compiler.compileFactory(angularCoreDiEnv, `ng:///${type.name}/ɵfac.js`, {
name: metadata.name,
type: metadata.type,
typeArgumentCount: metadata.typeArgumentCount,
name: type.name,
type,
typeArgumentCount: 0, // In JIT mode types are not available nor used.
deps: reflectDependencies(type),
target: compiler.FactoryTarget.Injectable
});
@ -94,23 +92,19 @@ function getInjectableMetadata(type: Type<any>, srcMeta?: Injectable): R3Injecta
type: type,
typeArgumentCount: 0,
providedIn: meta.providedIn,
userDeps: undefined,
};
if ((isUseClassProvider(meta) || isUseFactoryProvider(meta)) && meta.deps !== undefined) {
compilerMeta.userDeps = convertDependencies(meta.deps);
compilerMeta.deps = convertDependencies(meta.deps);
}
// Check to see if the user explicitly provided a `useXxxx` property.
if (isUseClassProvider(meta)) {
// The user explicitly specified useClass, and may or may not have provided deps.
compilerMeta.useClass = resolveForwardRef(meta.useClass);
compilerMeta.useClass = meta.useClass;
} else if (isUseValueProvider(meta)) {
// The user explicitly specified useValue.
compilerMeta.useValue = resolveForwardRef(meta.useValue);
compilerMeta.useValue = meta.useValue;
} else if (isUseFactoryProvider(meta)) {
// The user explicitly specified useFactory.
compilerMeta.useFactory = meta.useFactory;
} else if (isUseExistingProvider(meta)) {
// The user explicitly specified useExisting.
compilerMeta.useExisting = resolveForwardRef(meta.useExisting);
compilerMeta.useExisting = meta.useExisting;
}
return compilerMeta;
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {getCompilerFacade, R3DeclareComponentFacade, R3DeclareDirectiveFacade, R3DeclareFactoryFacade, R3DeclareInjectorFacade, R3DeclareNgModuleFacade, R3DeclarePipeFacade} from '../../compiler/compiler_facade';
import {getCompilerFacade, R3DeclareComponentFacade, R3DeclareDirectiveFacade, R3DeclareFactoryFacade, R3DeclareInjectableFacade, R3DeclareInjectorFacade, R3DeclareNgModuleFacade, R3DeclarePipeFacade} from '../../compiler/compiler_facade';
import {angularCoreEnv} from './environment';
/**
@ -42,6 +42,17 @@ export function ɵɵngDeclareFactory(decl: R3DeclareFactoryFacade): unknown {
angularCoreEnv, `ng:///${decl.type.name}/ɵfac.js`, decl);
}
/**
* Compiles a partial injectable declaration object into a full injectable definition object.
*
* @codeGenApi
*/
export function ɵɵngDeclareInjectable(decl: R3DeclareInjectableFacade): unknown {
const compiler = getCompilerFacade();
return compiler.compileInjectableDeclaration(
angularCoreEnv, `ng:///${decl.type.name}/ɵprov.js`, decl);
}
/**
* These enums are used in the partial factory declaration calls.
*/

View File

@ -657,8 +657,10 @@ describe('providers', () => {
constructor(public foo: SomeProvider) {}
}
TestBed.configureTestingModule(
{declarations: [App], providers: [{provide: SomeProvider, useClass: SomeProviderImpl}]});
// We don't configure the `SomeProvider` in the TestingModule so that it uses the
// tree-shakable provider given in the `@Injectable` decorator above, which makes use of the
// `forwardRef()`.
TestBed.configureTestingModule({declarations: [App]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();

View File

@ -0,0 +1,157 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* 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 {forwardRef, InjectionToken, Injector, ɵcreateInjector, ɵsetCurrentInjector, ɵɵdefineInjector, ɵɵInjectableDeclaration, ɵɵngDeclareInjectable, ɵɵngDeclareInjector, ɵɵngDeclareNgModule} from '@angular/core';
describe('Injectable declaration jit compilation', () => {
let previousInjector: Injector|null|undefined;
beforeEach(() => previousInjector = ɵsetCurrentInjector(ɵcreateInjector(TestInjector)));
afterEach(() => ɵsetCurrentInjector(previousInjector));
it('should compile a minimal injectable declaration that delegates to `ɵfac`', () => {
const provider = Minimal.ɵprov as ɵɵInjectableDeclaration<Minimal>;
expect((provider.token as any).name).toEqual('Minimal');
expect(provider.factory).toBe(Minimal.ɵfac);
const instance = provider.factory();
expect(instance).toBeInstanceOf(Minimal);
});
it('should compile a simple `useClass` injectable declaration', () => {
const provider = UseClass.ɵprov as ɵɵInjectableDeclaration<UseClass>;
expect((provider.token as any).name).toEqual('UseClass');
const instance = provider.factory();
expect(instance).toBeInstanceOf(UseClass);
});
it('should compile a simple `useFactory` injectable declaration', () => {
const provider = UseFactory.ɵprov as ɵɵInjectableDeclaration<UseFactory>;
expect((provider.token as any).name).toEqual('UseFactory');
const instance = provider.factory();
expect(instance).toBeInstanceOf(UseFactory);
expect(instance.msg).toEqual('from factory');
});
it('should compile a simple `useValue` injectable declaration', () => {
const provider = UseValue.ɵprov as ɵɵInjectableDeclaration<string>;
expect((provider.token as any).name).toEqual('UseValue');
const instance = provider.factory();
expect(instance).toEqual('a value');
});
it('should compile a simple `useExisting` injectable declaration', () => {
const provider = UseExisting.ɵprov as ɵɵInjectableDeclaration<string>;
expect((provider.token as any).name).toEqual('UseExisting');
const instance = provider.factory();
expect(instance).toEqual('existing');
});
it('should compile a `useClass` injectable declaration with dependencies', () => {
const provider = DependingClass.ɵprov as ɵɵInjectableDeclaration<DependingClass>;
expect((provider.token as any).name).toEqual('DependingClass');
const instance = provider.factory();
expect(instance).toBeInstanceOf(DependingClass);
expect(instance.testClass).toBeInstanceOf(UseClass);
});
it('should compile a `useFactory` injectable declaration with dependencies', () => {
const provider = DependingFactory.ɵprov as ɵɵInjectableDeclaration<DependingFactory>;
expect((provider.token as any).name).toEqual('DependingFactory');
const instance = provider.factory();
expect(instance).toBeInstanceOf(DependingFactory);
expect(instance.testClass).toBeInstanceOf(UseClass);
});
it('should unwrap a `ForwardRef` `useClass` injectable declaration', () => {
class TestClass {
static ɵprov = ɵɵngDeclareInjectable({
type: TestClass,
useClass: forwardRef(function() {
return FutureClass;
})
});
}
class FutureClass {
static ɵfac = () => new FutureClass();
}
const provider = TestClass.ɵprov as ɵɵInjectableDeclaration<FutureClass>;
const instance = provider.factory();
expect(instance).toBeInstanceOf(FutureClass);
});
it('should unwrap a `ForwardRef` `providedIn` injectable declaration', () => {
const expected = {};
class TestClass {
static ɵprov = ɵɵngDeclareInjectable({
type: TestClass,
providedIn: forwardRef(() => FutureModule),
useValue: expected,
});
}
class FutureModule {
static ɵinj = ɵɵngDeclareInjector({type: FutureModule});
}
const injector = ɵcreateInjector(FutureModule);
const actual = injector.get(TestClass);
expect(actual).toBe(expected);
});
});
class Minimal {
static ɵfac = () => new Minimal();
static ɵprov = ɵɵngDeclareInjectable({type: Minimal});
}
class UseClass {
static ɵprov = ɵɵngDeclareInjectable({type: UseClass, useClass: UseClass});
}
class UseFactory {
constructor(readonly msg: string) {}
static ɵprov =
ɵɵngDeclareInjectable({type: UseFactory, useFactory: () => new UseFactory('from factory')});
}
class UseValue {
constructor(readonly msg: string) {}
static ɵprov = ɵɵngDeclareInjectable({type: UseValue, useValue: 'a value'});
}
const UseExistingToken = new InjectionToken('UseExistingToken');
class UseExisting {
static ɵprov = ɵɵngDeclareInjectable({type: UseExisting, useExisting: UseExistingToken});
}
class DependingClass {
constructor(readonly testClass: UseClass) {}
static ɵprov = ɵɵngDeclareInjectable(
{type: DependingClass, useClass: DependingClass, deps: [{token: UseClass}]});
}
class DependingFactory {
constructor(readonly testClass: UseClass) {}
static ɵprov = ɵɵngDeclareInjectable({
type: DependingFactory,
useFactory: (dep: UseClass) => new DependingFactory(dep),
deps: [{token: UseClass}]
});
}
class TestInjector {
static ɵinj = ɵɵdefineInjector({
providers: [
UseClass,
UseFactory,
UseValue,
UseExisting,
DependingClass,
{provide: UseExistingToken, useValue: 'existing'},
],
});
}

View File

@ -14,6 +14,7 @@ import {angularCoreEnv} from '../../src/render3/jit/environment';
const INTERFACE_EXCEPTIONS = new Set<string>([
'ɵɵComponentDeclaration',
'ɵɵDirectiveDeclaration',
'ɵɵInjectableDeclaration',
'ɵɵInjectorDeclaration',
'ɵɵInjectorDef',
'ɵɵNgModuleDeclaration',
@ -30,6 +31,7 @@ const PARTIAL_ONLY = new Set<string>([
'ɵɵngDeclareDirective',
'ɵɵngDeclareComponent',
'ɵɵngDeclareFactory',
'ɵɵngDeclareInjectable',
'ɵɵngDeclareInjector',
'ɵɵngDeclareNgModule',
'ɵɵngDeclarePipe',