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 {toR3DirectiveMeta} from './partial_directive_linker_1';
import {PartialLinker} from './partial_linker'; import {PartialLinker} from './partial_linker';
import {extractForwardRef} from './util';
/** /**
* A `PartialLinker` that is designed to process `ɵɵngDeclareComponent()` call expressions. * 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 type = directiveExpr.getValue('type');
const selector = directiveExpr.getString('selector'); const selector = directiveExpr.getString('selector');
let typeExpr = type.getOpaque(); const {expression: typeExpr, isForwardRef} = extractForwardRef(type);
const forwardRefType = extractForwardRef(type); if (isForwardRef) {
if (forwardRefType !== null) {
typeExpr = forwardRefType;
declarationListEmitMode = DeclarationListEmitMode.Closure; declarationListEmitMode = DeclarationListEmitMode.Closure;
} }
@ -115,13 +114,11 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
let pipes = new Map<string, o.Expression>(); let pipes = new Map<string, o.Expression>();
if (metaObj.has('pipes')) { if (metaObj.has('pipes')) {
pipes = metaObj.getObject('pipes').toMap(pipe => { pipes = metaObj.getObject('pipes').toMap(pipe => {
const forwardRefType = extractForwardRef(pipe); const {expression: pipeType, isForwardRef} = extractForwardRef(pipe);
if (forwardRefType !== null) { if (isForwardRef) {
declarationListEmitMode = DeclarationListEmitMode.Closure; declarationListEmitMode = DeclarationListEmitMode.Closure;
return forwardRefType;
} else {
return pipe.getOpaque();
} }
return pipeType;
}); });
} }
@ -277,35 +274,3 @@ function parseChangeDetectionStrategy<TExpression>(
} }
return enumValue; 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 * 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 * 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 * as o from '@angular/compiler/src/output/output_ast';
import {AstObject} from '../../ast/ast_value'; import {AstObject} from '../../ast/ast_value';
import {FatalLinkerError} from '../../fatal_linker_error'; import {FatalLinkerError} from '../../fatal_linker_error';
import {PartialLinker} from './partial_linker'; 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. * A `PartialLinker` that is designed to process `ɵɵngDeclareFactory()` call expressions.
@ -45,11 +45,11 @@ export function toR3FactoryMeta<TExpression>(
internalType: metaObj.getOpaque('type'), internalType: metaObj.getOpaque('type'),
typeArgumentCount: 0, typeArgumentCount: 0,
target: parseEnum(metaObj.getValue('target'), FactoryTarget), target: parseEnum(metaObj.getValue('target'), FactoryTarget),
deps: getDeps(metaObj, 'deps'), deps: getDependencies(metaObj, 'deps'),
}; };
} }
function getDeps<TExpression>( function getDependencies<TExpression>(
metaObj: AstObject<R3DeclareFactoryMetadata, TExpression>, metaObj: AstObject<R3DeclareFactoryMetadata, TExpression>,
propName: keyof R3DeclareFactoryMetadata): R3DependencyMetadata[]|null|'invalid' { propName: keyof R3DeclareFactoryMetadata): R3DependencyMetadata[]|null|'invalid' {
if (!metaObj.has(propName)) { if (!metaObj.has(propName)) {
@ -57,32 +57,10 @@ function getDeps<TExpression>(
} }
const deps = metaObj.getValue(propName); const deps = metaObj.getValue(propName);
if (deps.isArray()) { if (deps.isArray()) {
return deps.getArray().map(dep => getDep(dep.getObject())); return deps.getArray().map(dep => getDependency(dep.getObject()));
} }
if (deps.isString()) { if (deps.isString()) {
return 'invalid'; return 'invalid';
} }
return null; 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 {PartialComponentLinkerVersion1} from './partial_component_linker_1';
import {PartialDirectiveLinkerVersion1} from './partial_directive_linker_1'; import {PartialDirectiveLinkerVersion1} from './partial_directive_linker_1';
import {PartialFactoryLinkerVersion1} from './partial_factory_linker_1'; import {PartialFactoryLinkerVersion1} from './partial_factory_linker_1';
import {PartialInjectableLinkerVersion1} from './partial_injectable_linker_1';
import {PartialInjectorLinkerVersion1} from './partial_injector_linker_1'; import {PartialInjectorLinkerVersion1} from './partial_injector_linker_1';
import {PartialLinker} from './partial_linker'; import {PartialLinker} from './partial_linker';
import {PartialNgModuleLinkerVersion1} from './partial_ng_module_linker_1'; 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 ɵɵngDeclareDirective = 'ɵɵngDeclareDirective';
export const ɵɵngDeclareComponent = 'ɵɵngDeclareComponent'; export const ɵɵngDeclareComponent = 'ɵɵngDeclareComponent';
export const ɵɵngDeclareFactory = 'ɵɵngDeclareFactory'; export const ɵɵngDeclareFactory = 'ɵɵngDeclareFactory';
export const ɵɵngDeclareInjectable = 'ɵɵngDeclareInjectable';
export const ɵɵngDeclareInjector = 'ɵɵngDeclareInjector'; export const ɵɵngDeclareInjector = 'ɵɵngDeclareInjector';
export const ɵɵngDeclareNgModule = 'ɵɵngDeclareNgModule'; export const ɵɵngDeclareNgModule = 'ɵɵngDeclareNgModule';
export const ɵɵngDeclarePipe = 'ɵɵngDeclarePipe'; export const ɵɵngDeclarePipe = 'ɵɵngDeclarePipe';
export const declarationFunctions = [ export const declarationFunctions = [
ɵɵngDeclareDirective, ɵɵngDeclareComponent, ɵɵngDeclareFactory, ɵɵngDeclareInjector, ɵɵngDeclareDirective, ɵɵngDeclareComponent, ɵɵngDeclareFactory, ɵɵngDeclareInjectable,
ɵɵngDeclareNgModule, ɵɵngDeclarePipe ɵɵngDeclareInjector, ɵɵngDeclareNgModule, ɵɵngDeclarePipe
]; ];
interface LinkerRange<TExpression> { interface LinkerRange<TExpression> {
@ -93,6 +95,7 @@ export class PartialLinkerSelector<TStatement, TExpression> {
environment, createGetSourceFile(sourceUrl, code, environment.sourceFileLoader), sourceUrl, environment, createGetSourceFile(sourceUrl, code, environment.sourceFileLoader), sourceUrl,
code); code);
const partialFactoryLinkerVersion1 = new PartialFactoryLinkerVersion1(); const partialFactoryLinkerVersion1 = new PartialFactoryLinkerVersion1();
const partialInjectableLinkerVersion1 = new PartialInjectableLinkerVersion1();
const partialInjectorLinkerVersion1 = new PartialInjectorLinkerVersion1(); const partialInjectorLinkerVersion1 = new PartialInjectorLinkerVersion1();
const partialNgModuleLinkerVersion1 = const partialNgModuleLinkerVersion1 =
new PartialNgModuleLinkerVersion1(environment.options.linkerJitMode); new PartialNgModuleLinkerVersion1(environment.options.linkerJitMode);
@ -111,6 +114,10 @@ export class PartialLinkerSelector<TStatement, TExpression> {
{range: '0.0.0-PLACEHOLDER', linker: partialFactoryLinkerVersion1}, {range: '0.0.0-PLACEHOLDER', linker: partialFactoryLinkerVersion1},
{range: '>=11.1.0-next.1', 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, [ linkers.set(ɵɵngDeclareInjector, [
{range: '0.0.0-PLACEHOLDER', linker: partialInjectorLinkerVersion1}, {range: '0.0.0-PLACEHOLDER', linker: partialInjectorLinkerVersion1},
{range: '>=11.1.0-next.1', 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 * 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 * 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 * 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'; import {FatalLinkerError} from '../../fatal_linker_error';
export function wrapReference<TExpression>(wrapped: o.WrappedNodeExpr<TExpression>): R3Reference { export function wrapReference<TExpression>(wrapped: o.WrappedNodeExpr<TExpression>): R3Reference {
@ -29,3 +30,66 @@ export function parseEnum<TExpression, TEnum>(
} }
return enumValue; 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 {PartialComponentLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_component_linker_1';
import {PartialDirectiveLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_directive_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 {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 {PartialInjectorLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_injector_linker_1';
import {PartialLinkerSelector} from '../../../src/file_linker/partial_linkers/partial_linker_selector'; import {PartialLinkerSelector} from '../../../src/file_linker/partial_linkers/partial_linker_selector';
import {PartialNgModuleLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_ng_module_linker_1'; 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('ɵɵngDeclareDirective')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareComponent')).toBe(true); expect(selector.supportsDeclaration('ɵɵngDeclareComponent')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareFactory')).toBe(true); expect(selector.supportsDeclaration('ɵɵngDeclareFactory')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareInjectable')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareInjector')).toBe(true); expect(selector.supportsDeclaration('ɵɵngDeclareInjector')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareNgModule')).toBe(true); expect(selector.supportsDeclaration('ɵɵngDeclareNgModule')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclarePipe')).toBe(true); expect(selector.supportsDeclaration('ɵɵngDeclarePipe')).toBe(true);
@ -66,6 +68,8 @@ describe('PartialLinkerSelector', () => {
.toBeInstanceOf(PartialComponentLinkerVersion1); .toBeInstanceOf(PartialComponentLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareFactory', '0.0.0-PLACEHOLDER')) expect(selector.getLinker('ɵɵngDeclareFactory', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialFactoryLinkerVersion1); .toBeInstanceOf(PartialFactoryLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareInjectable', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialInjectableLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareInjector', '0.0.0-PLACEHOLDER')) expect(selector.getLinker('ɵɵngDeclareInjector', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialInjectorLinkerVersion1); .toBeInstanceOf(PartialInjectorLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareNgModule', '0.0.0-PLACEHOLDER')) 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 {createValueHasWrongTypeError, getDirectiveDiagnostics, getProviderDiagnostics, getUndecoratedClassWithAngularFeaturesDiagnostic} from './diagnostics';
import {compileDeclareFactory, compileNgFactoryDefField} from './factory'; import {compileDeclareFactory, compileNgFactoryDefField} from './factory';
import {generateSetClassMetadataCall} from './metadata'; 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 EMPTY_OBJECT: {[key: string]: string} = {};
const FIELD_DECORATORS = [ const FIELD_DECORATORS = [
@ -528,7 +528,7 @@ export function extractQueryMetadata(
ErrorCode.DECORATOR_ARITY_WRONG, exprNode, `@${name} must have arguments`); ErrorCode.DECORATOR_ARITY_WRONG, exprNode, `@${name} must have arguments`);
} }
const first = name === 'ViewChild' || name === 'ContentChild'; 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); const arg = evaluator.evaluate(node);
/** Whether or not this query should collect only static results (see view/api.ts) */ /** 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'; import {CompileResult} from '../../transform';
export type CompileFactoryFn = (metadata: R3FactoryMetadata) => CompileResult;
export function compileNgFactoryDefField(metadata: R3FactoryMetadata): CompileResult { export function compileNgFactoryDefField(metadata: R3FactoryMetadata): CompileResult {
const res = compileFactoryFunction(metadata); const res = compileFactoryFunction(metadata);
return {name: 'ɵfac', initializer: res.expression, statements: res.statements, type: res.type}; 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 * 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 * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
@ -16,9 +16,9 @@ import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection'; import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
import {compileDeclareFactory, compileNgFactoryDefField} from './factory'; import {compileDeclareFactory, CompileFactoryFn, compileNgFactoryDefField} from './factory';
import {generateSetClassMetadataCall} from './metadata'; 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 { export interface InjectableHandlerData {
meta: R3InjectableMetadata; 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 export class InjectableDecoratorHandler implements
DecoratorHandler<Decorator, InjectableHandlerData, null, unknown> { DecoratorHandler<Decorator, InjectableHandlerData, null, unknown> {
@ -95,45 +95,25 @@ export class InjectableDecoratorHandler implements
} }
compileFull(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] { compileFull(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
const res = compileIvyInjectable(analysis.meta); return this.compile(
const statements = res.statements; compileNgFactoryDefField, meta => compileInjectable(meta, false), node, analysis);
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;
} }
compilePartial(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): compilePartial(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>):
CompileResult[] { CompileResult[] {
const res = compileIvyInjectable(analysis.meta); return this.compile(
const statements = res.statements; compileDeclareFactory, compileDeclareInjectableFromMetadata, node, analysis);
}
private compile(
compileFactoryFn: CompileFactoryFn,
compileInjectableFn: (meta: R3InjectableMetadata) => R3CompiledExpression,
node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
const results: CompileResult[] = []; const results: CompileResult[] = [];
if (analysis.needsFactory) { if (analysis.needsFactory) {
const meta = analysis.meta; const meta = analysis.meta;
const factoryRes = compileDeclareFactory( const factoryRes = compileFactoryFn(
toFactoryMetadata({...meta, deps: analysis.ctorDeps}, FactoryTarget.Injectable)); toFactoryMetadata({...meta, deps: analysis.ctorDeps}, FactoryTarget.Injectable));
if (analysis.metadataStmt !== null) { if (analysis.metadataStmt !== null) {
factoryRes.statements.push(analysis.metadataStmt); factoryRes.statements.push(analysis.metadataStmt);
@ -150,7 +130,9 @@ export class InjectableDecoratorHandler implements
if (ɵprov === undefined) { if (ɵprov === undefined) {
// Only add a new ɵprov if there is not one already // 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; return results;
@ -159,7 +141,7 @@ export class InjectableDecoratorHandler implements
/** /**
* Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the * 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. * A `null` return value indicates this is @Injectable has invalid data.
*/ */
@ -181,7 +163,7 @@ function extractInjectableMetadata(
type, type,
typeArgumentCount, typeArgumentCount,
internalType, internalType,
providedIn: new LiteralExpr(null), providedIn: createR3ProviderExpression(new LiteralExpr(null), false),
}; };
} else if (decorator.args.length === 1) { } else if (decorator.args.length === 1) {
const metaNode = decorator.args[0]; 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. // Resolve the fields of the literal into a map of field name to expression.
const meta = reflectObjectLiteral(metaNode); 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')) { if ((meta.has('useClass') || meta.has('useFactory')) && meta.has('deps')) {
const depsExpr = meta.get('deps')!; const depsExpr = meta.get('deps')!;
if (!ts.isArrayLiteralExpression(depsExpr)) { if (!ts.isArrayLiteralExpression(depsExpr)) {
@ -209,58 +191,42 @@ function extractInjectableMetadata(
ErrorCode.VALUE_NOT_LITERAL, depsExpr, ErrorCode.VALUE_NOT_LITERAL, depsExpr,
`@Injectable deps metadata must be an inline array`); `@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')) { if (meta.has('useValue')) {
return { result.useValue = getProviderExpression(meta.get('useValue')!, reflector);
name,
type,
typeArgumentCount,
internalType,
providedIn,
useValue: new WrappedNodeExpr(unwrapForwardRef(meta.get('useValue')!, reflector)),
};
} else if (meta.has('useExisting')) { } else if (meta.has('useExisting')) {
return { result.useExisting = getProviderExpression(meta.get('useExisting')!, reflector);
name,
type,
typeArgumentCount,
internalType,
providedIn,
useExisting: new WrappedNodeExpr(unwrapForwardRef(meta.get('useExisting')!, reflector)),
};
} else if (meta.has('useClass')) { } else if (meta.has('useClass')) {
return { result.useClass = getProviderExpression(meta.get('useClass')!, reflector);
name, result.deps = deps;
type,
typeArgumentCount,
internalType,
providedIn,
useClass: new WrappedNodeExpr(unwrapForwardRef(meta.get('useClass')!, reflector)),
userDeps,
};
} else if (meta.has('useFactory')) { } else if (meta.has('useFactory')) {
// useFactory is special - the 'deps' property must be analyzed. result.useFactory = new WrappedNodeExpr(meta.get('useFactory')!);
const factory = new WrappedNodeExpr(meta.get('useFactory')!); result.deps = deps;
return {
name,
type,
typeArgumentCount,
internalType,
providedIn,
useFactory: factory,
userDeps,
};
} else {
return {name, type, typeArgumentCount, internalType, providedIn};
} }
return result;
} else { } else {
throw new FatalDiagnosticError( throw new FatalDiagnosticError(
ErrorCode.DECORATOR_ARITY_WRONG, decorator.args[2], 'Too many arguments to @Injectable'); 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( function extractInjectableCtorDeps(
clazz: ClassDeclaration, meta: R3InjectableMetadata, decorator: Decorator, clazz: ClassDeclaration, meta: R3InjectableMetadata, decorator: Decorator,
reflector: ReflectionHost, defaultImportRecorder: DefaultImportRecorder, isCore: boolean, 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 node the forwardRef() expression to resolve
* @param reflector a ReflectionHost * @param reflector a ReflectionHost
* @returns the resolved expression, if the original expression was a forwardRef(), or the original * @returns the resolved expression, if the original expression was a forwardRef(), or `null`
* expression otherwise * 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); node = unwrapExpression(node);
if (!ts.isCallExpression(node) || node.arguments.length !== 1) { if (!ts.isCallExpression(node) || node.arguments.length !== 1) {
return node; return null;
} }
const fn = const fn =
ts.isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression; ts.isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression;
if (!ts.isIdentifier(fn)) { if (!ts.isIdentifier(fn)) {
return node; return null;
} }
const expr = expandForwardRef(node.arguments[0]); const expr = expandForwardRef(node.arguments[0]);
if (expr === null) { if (expr === null) {
return node; return null;
} }
const imp = reflector.getImportOfIdentifier(fn); const imp = reflector.getImportOfIdentifier(fn);
if (imp === null || imp.from !== '@angular/core' || imp.name !== 'forwardRef') { if (imp === null || imp.from !== '@angular/core' || imp.name !== 'forwardRef') {
return node; return null;
} else {
return expr;
} }
return expr;
} }
/** /**

View File

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

View File

@ -6,7 +6,7 @@ import * as i0 from "@angular/core";
export class MyService { export class MyService {
} }
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); 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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable type: Injectable
}], null, null); })(); }], null, null); })();
@ -80,7 +80,7 @@ export class MyService {
constructor(dep) { } constructor(dep) { }
} }
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [{ token: MyDependency }], target: i0.ɵɵFactoryTarget.Injectable }); 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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable type: Injectable
}], function () { return [{ type: MyDependency }]; }, null); })(); }], function () { return [{ type: MyDependency }]; }, null); })();
@ -111,7 +111,7 @@ export class MyService {
constructor(dep, optionalDep) { } 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.ɵ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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable type: Injectable
}], function () { return [{ type: MyDependency }, { type: MyOptionalDependency, decorators: [{ }], function () { return [{ type: MyDependency }, { type: MyOptionalDependency, decorators: [{
@ -144,7 +144,7 @@ function alternateFactory() {
export class MyService { export class MyService {
} }
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); 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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable, type: Injectable,
args: [{ providedIn: 'root', useFactory: alternateFactory }] args: [{ providedIn: 'root', useFactory: alternateFactory }]
@ -171,12 +171,7 @@ class MyAlternateService {
export class MyService { export class MyService {
} }
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); 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) { MyService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, providedIn: 'root', useFactory: () => new MyAlternateService(), deps: [{ token: SomeDep }] });
r = new t();
}
else {
r = (() => new MyAlternateService())(i0.ɵɵinject(SomeDep));
} return r; }, providedIn: 'root' });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable, type: Injectable,
args: [{ providedIn: 'root', useFactory: () => new MyAlternateService(), deps: [SomeDep] }] args: [{ providedIn: 'root', useFactory: () => new MyAlternateService(), deps: [SomeDep] }]
@ -199,14 +194,14 @@ import * as i0 from "@angular/core";
class MyAlternateService { class MyAlternateService {
} }
MyAlternateService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyAlternateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); 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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyAlternateService, [{
type: Injectable type: Injectable
}], null, null); })(); }], null, null); })();
export class MyService { export class MyService {
} }
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); 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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable, type: Injectable,
args: [{ providedIn: 'root', useClass: MyAlternateService }] args: [{ providedIn: 'root', useClass: MyAlternateService }]
@ -231,19 +226,14 @@ class SomeDep {
class MyAlternateService { class MyAlternateService {
} }
MyAlternateService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyAlternateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); 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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyAlternateService, [{
type: Injectable type: Injectable
}], null, null); })(); }], null, null); })();
export class MyService { export class MyService {
} }
MyService.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); 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) { MyService.ɵprov = i0.ɵɵngDeclareInjectable({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, providedIn: 'root', useClass: MyAlternateService, deps: [{ token: SomeDep }] });
r = new t();
}
else {
r = new MyAlternateService(i0.ɵɵinject(SomeDep));
} return r; }, providedIn: 'root' });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyService, [{
type: Injectable, type: Injectable,
args: [{ providedIn: 'root', useClass: MyAlternateService, deps: [SomeDep] }] args: [{ providedIn: 'root', useClass: MyAlternateService, deps: [SomeDep] }]
@ -266,7 +256,7 @@ import * as i0 from "@angular/core";
class SomeProvider { class SomeProvider {
} }
SomeProvider.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); 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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SomeProvider, [{
type: Injectable, type: Injectable,
args: [{ providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl) }] args: [{ providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl) }]
@ -274,7 +264,7 @@ SomeProvider.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: SomeProvide
class SomeProviderImpl extends SomeProvider { class SomeProviderImpl extends SomeProvider {
} }
SomeProviderImpl.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeProviderImpl, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); 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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SomeProviderImpl, [{
type: Injectable type: Injectable
}], null, null); })(); }], null, null); })();
@ -284,6 +274,55 @@ SomeProviderImpl.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: SomePro
****************************************************************************************************/ ****************************************************************************************************/
export {}; 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 * PARTIAL FILE: pipe_and_injectable.js
****************************************************************************************************/ ****************************************************************************************************/
@ -292,7 +331,7 @@ import * as i0 from "@angular/core";
class Service { class Service {
} }
Service.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Service, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); 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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Service, [{
type: Injectable type: Injectable
}], null, null); })(); }], 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.ɵ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.ɵ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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyPipe, [{
type: Injectable 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.ɵ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.ɵ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, [{ (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyOtherPipe, [{
type: Pipe, type: Pipe,
args: [{ name: 'myOtherPipe' }] 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", "description": "should have the pipe factory take precedence over the injectable factory, if a class has multiple decorators",
"inputFiles": [ "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) expect(jsContents)
.toContain( .toContain(
'Service.ɵfac = function Service_Factory(t) { return new (t || Service)(i0.ɵɵinject(Dep)); };'); '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'); expect(jsContents).not.toContain('__decorate');
const dtsContents = env.getContents('test.d.ts'); const dtsContents = env.getContents('test.d.ts');
expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDef<Dep>;'); expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDeclaration<Dep>;');
expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDef<Service>;'); expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDeclaration<Service>;');
expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration<Dep, never>;'); expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration<Dep, never>;');
expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration<Service, never>;'); expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration<Service, never>;');
expect(dtsContents).toContain('i0.ɵɵFactoryDeclaration<Mod, never>;'); expect(dtsContents).toContain('i0.ɵɵFactoryDeclaration<Mod, never>;');

View File

@ -36,6 +36,11 @@ export function identifierName(compileIdentifier: CompileIdentifierMetadata|null
if (ref['__anonymousType']) { if (ref['__anonymousType']) {
return 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); let identifier = stringify(ref);
if (identifier.indexOf('(') >= 0) { if (identifier.indexOf('(') >= 0) {
// case: anonymous functions! // case: anonymous functions!

View File

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

View File

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

View File

@ -66,9 +66,6 @@ export class Identifiers {
static directiveInject: o.ExternalReference = {name: 'ɵɵdirectiveInject', moduleName: CORE}; 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 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 = { static ViewEncapsulation: o.ExternalReference = {
name: 'ViewEncapsulation', name: 'ViewEncapsulation',
moduleName: CORE, moduleName: CORE,

View File

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

View File

@ -6,32 +6,62 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Identifiers} from './identifiers';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
import {generateForwardRef} from './render3/partial/util';
import {compileFactoryFunction, FactoryTarget, R3DependencyMetadata, R3FactoryDelegateType, R3FactoryMetadata} from './render3/r3_factory'; 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'; import {DefinitionMap} from './render3/view/util';
export interface InjectableDef {
expression: o.Expression;
type: o.Type;
statements: o.Statement[];
}
export interface R3InjectableMetadata { export interface R3InjectableMetadata {
name: string; name: string;
type: R3Reference; type: R3Reference;
internalType: o.Expression; internalType: o.Expression;
typeArgumentCount: number; typeArgumentCount: number;
providedIn: o.Expression; providedIn: R3ProviderExpression;
useClass?: o.Expression; useClass?: R3ProviderExpression;
useFactory?: o.Expression; useFactory?: o.Expression;
useExisting?: o.Expression; useExisting?: R3ProviderExpression;
useValue?: o.Expression; useValue?: R3ProviderExpression;
userDeps?: R3DependencyMetadata[]; 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; let result: {expression: o.Expression, statements: o.Statement[]}|null = null;
const factoryMeta: R3FactoryMetadata = { 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 // 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. // 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; let deps: R3DependencyMetadata[]|undefined = undefined;
if (meta.userDeps !== undefined) { if (meta.deps !== undefined) {
deps = meta.userDeps; deps = meta.deps;
} }
if (deps !== undefined) { if (deps !== undefined) {
// factory: () => new meta.useClass(...deps) // factory: () => new meta.useClass(...deps)
result = compileFactoryFunction({ result = compileFactoryFunction({
...factoryMeta, ...factoryMeta,
delegate: meta.useClass, delegate: meta.useClass.expression,
delegateDeps: deps, delegateDeps: deps,
delegateType: R3FactoryDelegateType.Class, delegateType: R3FactoryDelegateType.Class,
}); });
} else if (useClassOnSelf) { } else if (useClassOnSelf) {
result = compileFactoryFunction(factoryMeta); result = compileFactoryFunction(factoryMeta);
} else { } else {
result = delegateToFactory( result = {
meta.type.value as o.WrappedNodeExpr<any>, meta.useClass as o.WrappedNodeExpr<any>); statements: [],
expression: delegateToFactory(
meta.type.value as o.WrappedNodeExpr<any>,
meta.useClass.expression as o.WrappedNodeExpr<any>, resolveForwardRefs)
};
} }
} else if (meta.useFactory !== undefined) { } else if (meta.useFactory !== undefined) {
if (meta.userDeps !== undefined) { if (meta.deps !== undefined) {
result = compileFactoryFunction({ result = compileFactoryFunction({
...factoryMeta, ...factoryMeta,
delegate: meta.useFactory, delegate: meta.useFactory,
delegateDeps: meta.userDeps || [], delegateDeps: meta.deps || [],
delegateType: R3FactoryDelegateType.Function, delegateType: R3FactoryDelegateType.Function,
}); });
} else { } else {
@ -91,17 +125,21 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
// value is undefined. // value is undefined.
result = compileFactoryFunction({ result = compileFactoryFunction({
...factoryMeta, ...factoryMeta,
expression: meta.useValue, expression: meta.useValue.expression,
}); });
} else if (meta.useExisting !== undefined) { } else if (meta.useExisting !== undefined) {
// useExisting is an `inject` call on the existing token. // useExisting is an `inject` call on the existing token.
result = compileFactoryFunction({ result = compileFactoryFunction({
...factoryMeta, ...factoryMeta,
expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting]), expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting.expression]),
}); });
} else { } else {
result = delegateToFactory( result = {
meta.type.value as o.WrappedNodeExpr<any>, meta.internalType as o.WrappedNodeExpr<any>); statements: [],
expression: delegateToFactory(
meta.type.value as o.WrappedNodeExpr<any>, meta.internalType as o.WrappedNodeExpr<any>,
resolveForwardRefs)
};
} }
const token = meta.internalType; const token = meta.internalType;
@ -112,32 +150,59 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
injectableProps.set('factory', result.expression); injectableProps.set('factory', result.expression);
// Only generate providedIn property if it has a non-null value // Only generate providedIn property if it has a non-null value
if ((meta.providedIn as o.LiteralExpr).value !== null) { if ((meta.providedIn.expression as o.LiteralExpr).value !== null) {
injectableProps.set('providedIn', meta.providedIn); injectableProps.set(
'providedIn',
meta.providedIn.isForwardRef ? generateForwardRef(meta.providedIn.expression) :
meta.providedIn.expression);
} }
const expression = o.importExpr(Identifiers.ɵɵdefineInjectable) const expression = o.importExpr(Identifiers.ɵɵdefineInjectable)
.callFn([injectableProps.toLiteralMap()], undefined, true); .callFn([injectableProps.toLiteralMap()], undefined, true);
const type = new o.ExpressionType(o.importExpr(
Identifiers.InjectableDeclaration,
[typeWithParameters(meta.type.type, meta.typeArgumentCount)]));
return { return {
expression, expression,
type, type: createInjectableType(meta),
statements: result.statements, statements: result.statements,
}; };
} }
function delegateToFactory(type: o.WrappedNodeExpr<any>, internalType: o.WrappedNodeExpr<any>) { export function createInjectableType(meta: R3InjectableMetadata) {
return { return new o.ExpressionType(o.importExpr(
statements: [], Identifiers.InjectableDeclaration,
// If types are the same, we can generate `factory: type.ɵfac` [typeWithParameters(meta.type.type, meta.typeArgumentCount)]));
// 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 ? function delegateToFactory(
internalType.prop('ɵfac') : type: o.WrappedNodeExpr<any>, internalType: o.WrappedNodeExpr<any>,
o.fn([new o.FnParam('t', o.DYNAMIC_TYPE)], [new o.ReturnStatement(internalType.callMethod( unwrapForwardRefs: boolean): o.Expression {
'ɵfac', [o.variable('t')]))]) 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 {ConstantPool} from './constant_pool';
import {ChangeDetectionStrategy, HostBinding, HostListener, Input, Output, Type, ViewEncapsulation} from './core'; import {ChangeDetectionStrategy, HostBinding, HostListener, Input, Output, ViewEncapsulation} from './core';
import {compileInjectable} from './injectable_compiler_2'; import {compileInjectable, createR3ProviderExpression, R3ProviderExpression} from './injectable_compiler_2';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/interpolation_config'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/interpolation_config';
import {DeclareVarStmt, Expression, literal, LiteralExpr, Statement, StmtModifier, WrappedNodeExpr} from './output/output_ast'; import {DeclareVarStmt, Expression, literal, LiteralExpr, Statement, StmtModifier, WrappedNodeExpr} from './output/output_ast';
import {JitEvaluator} from './output/output_jit'; import {JitEvaluator} from './output/output_jit';
@ -60,18 +60,41 @@ export class CompilerFacadeImpl implements CompilerFacade {
compileInjectable( compileInjectable(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
facade: R3InjectableMetadataFacade): any { facade: R3InjectableMetadataFacade): any {
const {expression, statements} = compileInjectable({ const {expression, statements} = compileInjectable(
{
name: facade.name, name: facade.name,
type: wrapReference(facade.type), type: wrapReference(facade.type),
internalType: new WrappedNodeExpr(facade.type), internalType: new WrappedNodeExpr(facade.type),
typeArgumentCount: facade.typeArgumentCount, typeArgumentCount: facade.typeArgumentCount,
providedIn: computeProvidedIn(facade.providedIn), providedIn: computeProvidedIn(facade.providedIn),
useClass: wrapExpression(facade, USE_CLASS), useClass: convertToProviderExpression(facade, USE_CLASS),
useFactory: wrapExpression(facade, USE_FACTORY), useFactory: wrapExpression(facade, USE_FACTORY),
useValue: wrapExpression(facade, USE_VALUE), useValue: convertToProviderExpression(facade, USE_VALUE),
useExisting: wrapExpression(facade, USE_EXISTING), useExisting: convertToProviderExpression(facade, USE_EXISTING),
userDeps: convertR3DependencyMetadataArray(facade.userDeps) || undefined, 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); return this.jitExpression(expression, angularCoreEnv, sourceMapUrl, statements);
} }
@ -446,6 +469,22 @@ function parseJitTemplate(
type R3DirectiveMetadataFacadeNoPropAndWhitespace = type R3DirectiveMetadataFacadeNoPropAndWhitespace =
Pick<R3DirectiveMetadataFacade, Exclude<keyof R3DirectiveMetadataFacade, 'propMetadata'>>; 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 { function wrapExpression(obj: any, property: string): WrappedNodeExpr<any>|undefined {
if (obj.hasOwnProperty(property)) { if (obj.hasOwnProperty(property)) {
return new WrappedNodeExpr(obj[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 { function computeProvidedIn(providedIn: Function|string|null|undefined): R3ProviderExpression {
if (providedIn == null || typeof providedIn === 'string') { const expression = (providedIn == null || typeof providedIn === 'string') ?
return new LiteralExpr(providedIn); new LiteralExpr(providedIn ?? null) :
} else { new WrappedNodeExpr(providedIn);
return new WrappedNodeExpr(providedIn); // See `convertToProviderExpression()` for why `isForwardRef` is false.
} return createR3ProviderExpression(expression, /* isForwardRef */ false);
} }
function convertR3DependencyMetadataArray(facades: R3DependencyMetadataFacade[]|null| function convertR3DependencyMetadataArray(facades: R3DependencyMetadataFacade[]|null|

View File

@ -367,6 +367,53 @@ export enum FactoryTarget {
NgModule = 4, 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. * 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 {R3DeclareComponentMetadata, R3DeclareUsedDirectiveMetadata} from './api';
import {createDirectiveDefinitionMap} from './directive'; 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); 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 * found in the LICENSE file at https://angular.io/license
*/ */
import * as o from '../../output/output_ast'; 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 {Identifiers as R3} from '../r3_identifiers';
import {R3CompiledExpression} from '../util'; import {R3CompiledExpression} from '../util';
import {DefinitionMap} from '../view/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 { export function compileDeclareFactoryFunction(meta: R3FactoryMetadata): R3CompiledExpression {
const definitionMap = new DefinitionMap<R3DeclareFactoryMetadata>(); const definitionMap = new DefinitionMap<R3DeclareFactoryMetadata>();
@ -27,35 +28,3 @@ export function compileDeclareFactoryFunction(meta: R3FactoryMetadata): R3Compil
type: createFactoryType(meta), 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: []}; return {expression, type, statements: []};
} }
/**
* Gathers the declaration fields for an Injector into a `DefinitionMap`.
*/
function createInjectorDefinitionMap(meta: R3InjectorMetadata): function createInjectorDefinitionMap(meta: R3InjectorMetadata):
DefinitionMap<R3DeclareInjectorMetadata> { DefinitionMap<R3DeclareInjectorMetadata> {
const definitionMap = new DefinitionMap<R3DeclareInjectorMetadata>(); const definitionMap = new DefinitionMap<R3DeclareInjectorMetadata>();

View File

@ -23,6 +23,9 @@ export function compileDeclareNgModuleFromMetadata(meta: R3NgModuleMetadata): R3
return {expression, type, statements: []}; return {expression, type, statements: []};
} }
/**
* Gathers the declaration fields for an NgModule into a `DefinitionMap`.
*/
function createNgModuleDefinitionMap(meta: R3NgModuleMetadata): function createNgModuleDefinitionMap(meta: R3NgModuleMetadata):
DefinitionMap<R3DeclareNgModuleMetadata> { DefinitionMap<R3DeclareNgModuleMetadata> {
const definitionMap = new 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 * Gathers the declaration fields for a Pipe into a `DefinitionMap`.
* this logic for components, as they extend the Pipe metadata.
*/ */
export function createPipeDefinitionMap(meta: R3PipeMetadata): export function createPipeDefinitionMap(meta: R3PipeMetadata):
DefinitionMap<R3DeclarePipeMetadata> { DefinitionMap<R3DeclarePipeMetadata> {

View File

@ -6,6 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as o from '../../output/output_ast'; 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 * 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; 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 forwardRef: o.ExternalReference = {name: 'forwardRef', moduleName: CORE};
static resolveForwardRef: o.ExternalReference = {name: 'resolveForwardRef', 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 resolveWindow: o.ExternalReference = {name: 'ɵɵresolveWindow', moduleName: CORE};
static resolveDocument: o.ExternalReference = {name: 'ɵɵresolveDocument', moduleName: CORE}; static resolveDocument: o.ExternalReference = {name: 'ɵɵresolveDocument', moduleName: CORE};
static resolveBody: o.ExternalReference = {name: 'ɵɵresolveBody', moduleName: CORE}; static resolveBody: o.ExternalReference = {name: 'ɵɵresolveBody', moduleName: CORE};

View File

@ -76,6 +76,11 @@ const coreR3InjectableMetadataFacade: core.R3InjectableMetadataFacade =
const compilerR3InjectableMetadataFacade: compiler.R3InjectableMetadataFacade = const compilerR3InjectableMetadataFacade: compiler.R3InjectableMetadataFacade =
null! as core.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 = const coreR3NgModuleMetadataFacade: core.R3NgModuleMetadataFacade =
null! as compiler.R3NgModuleMetadataFacade; null! as compiler.R3NgModuleMetadataFacade;
const compilerR3NgModuleMetadataFacade: compiler.R3NgModuleMetadataFacade = const compilerR3NgModuleMetadataFacade: compiler.R3NgModuleMetadataFacade =

View File

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

View File

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

View File

@ -5,6 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {resolveForwardRef} from '../forward_ref';
import {ɵɵinject, ɵɵinvalidFactoryDep} from '../injector_compatibility'; import {ɵɵinject, ɵɵinvalidFactoryDep} from '../injector_compatibility';
import {ɵɵdefineInjectable, ɵɵdefineInjector} from '../interface/defs'; import {ɵɵdefineInjectable, ɵɵdefineInjector} from '../interface/defs';
@ -18,4 +19,5 @@ export const angularCoreDiEnv: {[name: string]: Function} = {
'ɵɵdefineInjector': ɵɵdefineInjector, 'ɵɵdefineInjector': ɵɵdefineInjector,
'ɵɵinject': ɵɵinject, 'ɵɵinject': ɵɵinject,
'ɵɵinvalidFactoryDep': ɵɵinvalidFactoryDep, 'ɵɵ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 * Compile an Angular injectable according to its `Injectable` metadata, and patch the resulting
* injectable def (`ɵprov`) onto the injectable type. * 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 ngInjectableDef: any = null;
let ngFactoryDef: any = null; let ngFactoryDef: any = null;
@ -34,8 +34,7 @@ export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
get: () => { get: () => {
if (ngInjectableDef === null) { if (ngInjectableDef === null) {
ngInjectableDef = getCompilerFacade().compileInjectable( ngInjectableDef = getCompilerFacade().compileInjectable(
angularCoreDiEnv, `ng:///${type.name}/ɵprov.js`, angularCoreDiEnv, `ng:///${type.name}/ɵprov.js`, getInjectableMetadata(type, meta));
getInjectableMetadata(type, srcMeta));
} }
return ngInjectableDef; return ngInjectableDef;
}, },
@ -47,12 +46,11 @@ export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
Object.defineProperty(type, NG_FACTORY_DEF, { Object.defineProperty(type, NG_FACTORY_DEF, {
get: () => { get: () => {
if (ngFactoryDef === null) { if (ngFactoryDef === null) {
const metadata = getInjectableMetadata(type, srcMeta);
const compiler = getCompilerFacade(); const compiler = getCompilerFacade();
ngFactoryDef = compiler.compileFactory(angularCoreDiEnv, `ng:///${type.name}/ɵfac.js`, { ngFactoryDef = compiler.compileFactory(angularCoreDiEnv, `ng:///${type.name}/ɵfac.js`, {
name: metadata.name, name: type.name,
type: metadata.type, type,
typeArgumentCount: metadata.typeArgumentCount, typeArgumentCount: 0, // In JIT mode types are not available nor used.
deps: reflectDependencies(type), deps: reflectDependencies(type),
target: compiler.FactoryTarget.Injectable target: compiler.FactoryTarget.Injectable
}); });
@ -94,23 +92,19 @@ function getInjectableMetadata(type: Type<any>, srcMeta?: Injectable): R3Injecta
type: type, type: type,
typeArgumentCount: 0, typeArgumentCount: 0,
providedIn: meta.providedIn, providedIn: meta.providedIn,
userDeps: undefined,
}; };
if ((isUseClassProvider(meta) || isUseFactoryProvider(meta)) && meta.deps !== 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)) { if (isUseClassProvider(meta)) {
// The user explicitly specified useClass, and may or may not have provided deps. compilerMeta.useClass = meta.useClass;
compilerMeta.useClass = resolveForwardRef(meta.useClass);
} else if (isUseValueProvider(meta)) { } else if (isUseValueProvider(meta)) {
// The user explicitly specified useValue. compilerMeta.useValue = meta.useValue;
compilerMeta.useValue = resolveForwardRef(meta.useValue);
} else if (isUseFactoryProvider(meta)) { } else if (isUseFactoryProvider(meta)) {
// The user explicitly specified useFactory.
compilerMeta.useFactory = meta.useFactory; compilerMeta.useFactory = meta.useFactory;
} else if (isUseExistingProvider(meta)) { } else if (isUseExistingProvider(meta)) {
// The user explicitly specified useExisting. compilerMeta.useExisting = meta.useExisting;
compilerMeta.useExisting = resolveForwardRef(meta.useExisting);
} }
return compilerMeta; return compilerMeta;
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * 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'; import {angularCoreEnv} from './environment';
/** /**
@ -42,6 +42,17 @@ export function ɵɵngDeclareFactory(decl: R3DeclareFactoryFacade): unknown {
angularCoreEnv, `ng:///${decl.type.name}/ɵfac.js`, decl); 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. * These enums are used in the partial factory declaration calls.
*/ */

View File

@ -657,8 +657,10 @@ describe('providers', () => {
constructor(public foo: SomeProvider) {} constructor(public foo: SomeProvider) {}
} }
TestBed.configureTestingModule( // We don't configure the `SomeProvider` in the TestingModule so that it uses the
{declarations: [App], providers: [{provide: SomeProvider, useClass: SomeProviderImpl}]}); // tree-shakable provider given in the `@Injectable` decorator above, which makes use of the
// `forwardRef()`.
TestBed.configureTestingModule({declarations: [App]});
const fixture = TestBed.createComponent(App); const fixture = TestBed.createComponent(App);
fixture.detectChanges(); 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>([ const INTERFACE_EXCEPTIONS = new Set<string>([
'ɵɵComponentDeclaration', 'ɵɵComponentDeclaration',
'ɵɵDirectiveDeclaration', 'ɵɵDirectiveDeclaration',
'ɵɵInjectableDeclaration',
'ɵɵInjectorDeclaration', 'ɵɵInjectorDeclaration',
'ɵɵInjectorDef', 'ɵɵInjectorDef',
'ɵɵNgModuleDeclaration', 'ɵɵNgModuleDeclaration',
@ -30,6 +31,7 @@ const PARTIAL_ONLY = new Set<string>([
'ɵɵngDeclareDirective', 'ɵɵngDeclareDirective',
'ɵɵngDeclareComponent', 'ɵɵngDeclareComponent',
'ɵɵngDeclareFactory', 'ɵɵngDeclareFactory',
'ɵɵngDeclareInjectable',
'ɵɵngDeclareInjector', 'ɵɵngDeclareInjector',
'ɵɵngDeclareNgModule', 'ɵɵngDeclareNgModule',
'ɵɵngDeclarePipe', 'ɵɵngDeclarePipe',