feat(ivy): @NgModule -> ngInjectorDef compilation (#22458)

This adds compilation of @NgModule providers and imports into
ngInjectorDef statements in generated code. All @NgModule annotations
will be compiled and the @NgModule decorators removed from the
resultant js output.

All @Injectables will also be compiled in Ivy mode, and the decorator
removed.

PR Close #22458
This commit is contained in:
Alex Rickabaugh 2018-02-16 08:45:21 -08:00 committed by Miško Hevery
parent 688096b7a3
commit 6ef9f2278f
48 changed files with 2044 additions and 206 deletions

View File

@ -3,7 +3,7 @@
"master": {
"uncompressed": {
"inline": 1447,
"main": 155112,
"main": 157654,
"polyfills": 59179
}
}
@ -11,7 +11,7 @@
"hello_world__closure": {
"master": {
"uncompressed": {
"bundle": 105779
"bundle": 106550
}
}
},

View File

@ -79,7 +79,13 @@ def _expected_outs(ctx):
i18n_messages = i18n_messages_files,
)
def _ivy_tsconfig(ctx, files, srcs, **kwargs):
return _ngc_tsconfig_helper(ctx, files, srcs, True, **kwargs)
def _ngc_tsconfig(ctx, files, srcs, **kwargs):
return _ngc_tsconfig_helper(ctx, files, srcs, False, **kwargs)
def _ngc_tsconfig_helper(ctx, files, srcs, enable_ivy, **kwargs):
outs = _expected_outs(ctx)
if "devmode_manifest" in kwargs:
expected_outs = outs.devmode_js + outs.declarations + outs.summaries
@ -92,6 +98,7 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
"generateCodeForLibraries": False,
"allowEmptyCodegenFiles": True,
"enableSummariesForJit": True,
"enableIvy": enable_ivy,
"fullTemplateTypeCheck": ctx.attr.type_check,
# FIXME: wrong place to de-dupe
"expectedOut": depset([o.path for o in expected_outs]).to_list()
@ -283,7 +290,7 @@ def _write_bundle_index(ctx):
)
return outputs
def ng_module_impl(ctx, ts_compile_actions):
def ng_module_impl(ctx, ts_compile_actions, ivy = False):
"""Implementation function for the ng_module rule.
This is exposed so that google3 can have its own entry point that re-uses this
@ -292,16 +299,19 @@ def ng_module_impl(ctx, ts_compile_actions):
Args:
ctx: the skylark rule context
ts_compile_actions: generates all the actions to run an ngc compilation
ivy: if True, run the compiler in Ivy mode (internal only)
Returns:
the result of the ng_module rule as a dict, suitable for
conversion by ts_providers_dict_to_struct
"""
tsconfig = _ngc_tsconfig if not ivy else _ivy_tsconfig
providers = ts_compile_actions(
ctx, is_library=True, compile_action=_prodmode_compile_action,
devmode_compile_action=_devmode_compile_action,
tsc_wrapped_tsconfig=_ngc_tsconfig,
tsc_wrapped_tsconfig=tsconfig,
outputs = _ts_expected_outs)
outs = _expected_outs(ctx)
@ -325,6 +335,9 @@ def ng_module_impl(ctx, ts_compile_actions):
def _ng_module_impl(ctx):
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts))
def _ivy_module_impl(ctx):
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts, True))
NG_MODULE_ATTRIBUTES = {
"srcs": attr.label_list(allow_files = [".ts"]),
@ -363,24 +376,35 @@ NG_MODULE_ATTRIBUTES = {
"_supports_workers": attr.bool(default = True),
}
NG_MODULE_RULE_ATTRS = dict(dict(COMMON_ATTRIBUTES, **NG_MODULE_ATTRIBUTES), **{
"tsconfig": attr.label(allow_files = True, single_file = True),
# @// is special syntax for the "main" repository
# The default assumes the user specified a target "node_modules" in their
# root BUILD file.
"node_modules": attr.label(
default = Label("@//:node_modules")
),
"entry_point": attr.string(),
"_index_bundler": attr.label(
executable = True,
cfg = "host",
default = Label("//packages/bazel/src:index_bundler")),
})
ng_module = rule(
implementation = _ng_module_impl,
attrs = dict(dict(COMMON_ATTRIBUTES, **NG_MODULE_ATTRIBUTES), **{
"tsconfig": attr.label(allow_files = True, single_file = True),
# @// is special syntax for the "main" repository
# The default assumes the user specified a target "node_modules" in their
# root BUILD file.
"node_modules": attr.label(
default = Label("@//:node_modules")
),
"entry_point": attr.string(),
"_index_bundler": attr.label(
executable = True,
cfg = "host",
default = Label("//packages/bazel/src:index_bundler")),
}),
attrs = NG_MODULE_RULE_ATTRS,
outputs = COMMON_OUTPUTS,
)
# TODO(alxhub): this rule exists to allow early testing of the Ivy compiler within angular/angular,
# and should not be made public. When ng_module() supports Ivy-mode outputs, this rule should be
# removed and its usages refactored to use ng_module() directly.
internal_ivy_ng_module = rule(
implementation = _ivy_module_impl,
attrs = NG_MODULE_RULE_ATTRS,
outputs = COMMON_OUTPUTS,
)

View File

@ -0,0 +1,18 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ivy_ng_module", "ts_library")
load("//packages/bazel/src:ng_rollup_bundle.bzl", "ng_rollup_bundle")
ivy_ng_module(
name = "app",
srcs = glob(
[
"src/**/*.ts",
],
),
module_name = "app_built",
deps = [
"//packages/core",
"@rxjs",
],
)

View File

@ -0,0 +1,40 @@
/**
* @license
* Copyright Google Inc. 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 {Injectable, InjectionToken, NgModule} from '@angular/core';
export const AOT_TOKEN = new InjectionToken<string>('TOKEN');
@Injectable()
export class AotService {
}
@NgModule({
providers: [AotService],
})
export class AotServiceModule {
}
@NgModule({
providers: [{provide: AOT_TOKEN, useValue: 'imports'}],
})
export class AotImportedModule {
}
@NgModule({
providers: [{provide: AOT_TOKEN, useValue: 'exports'}],
})
export class AotExportedModule {
}
@NgModule({
imports: [AotServiceModule, AotImportedModule],
exports: [AotExportedModule],
})
export class AotModule {
}

View File

@ -0,0 +1,27 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
ts_library(
name = "test_lib",
testonly = 1,
srcs = glob(
[
"**/*.ts",
],
),
deps = [
"//packages/compiler-cli/integrationtest/bazel/injector_def/ivy_build/app",
"//packages/core",
],
)
jasmine_node_test(
name = "test",
bootstrap = ["angular/tools/testing/init_node_spec.js"],
deps = [
":test_lib",
"//tools/testing:node",
],
)

View File

@ -0,0 +1,84 @@
/**
* @license
* Copyright Google Inc. 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 {Injectable, InjectionToken, Injector, NgModule, createInjector, forwardRef} from '@angular/core';
import {AOT_TOKEN, AotModule, AotService} from 'app_built/src/module';
describe('Ivy NgModule', () => {
describe('AOT', () => {
let injector: Injector;
beforeEach(() => { injector = createInjector(AotModule); });
it('works', () => { expect(injector.get(AotService) instanceof AotService).toBeTruthy(); });
it('merges imports and exports', () => { expect(injector.get(AOT_TOKEN)).toEqual('exports'); });
});
describe('JIT', () => {
@Injectable({providedIn: null})
class Service {
}
@NgModule({
providers: [Service],
})
class JitModule {
}
@NgModule({
imports: [JitModule],
})
class JitAppModule {
}
it('works', () => { createInjector(JitAppModule); });
it('throws an error on circular module dependencies', () => {
@NgModule({
imports: [forwardRef(() => BModule)],
})
class AModule {
}
@NgModule({
imports: [AModule],
})
class BModule {
}
expect(() => createInjector(AModule))
.toThrowError('Circular dependency: type AModule ends up importing itself.');
});
it('merges imports and exports', () => {
const TOKEN = new InjectionToken<string>('TOKEN');
@NgModule({
providers: [{provide: TOKEN, useValue: 'provided from A'}],
})
class AModule {
}
@NgModule({
providers: [{provide: TOKEN, useValue: 'provided from B'}],
})
class BModule {
}
@NgModule({
imports: [AModule],
exports: [BModule],
})
class CModule {
}
const injector = createInjector(CModule);
expect(injector.get(TOKEN)).toEqual('provided from B');
});
});
});

View File

@ -208,7 +208,7 @@ interface MetadataAndLoweringRequests {
requests: RequestLocationMap;
}
function shouldLower(node: ts.Node | undefined): boolean {
function isEligibleForLowering(node: ts.Node | undefined): boolean {
if (node) {
switch (node.kind) {
case ts.SyntaxKind.SourceFile:
@ -226,7 +226,7 @@ function shouldLower(node: ts.Node | undefined): boolean {
// Avoid lowering expressions already in an exported variable declaration
return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) == 0;
}
return shouldLower(node.parent);
return isEligibleForLowering(node.parent);
}
return true;
}
@ -251,11 +251,14 @@ function isLiteralFieldNamed(node: ts.Node, names: Set<string>): boolean {
return false;
}
const LOWERABLE_FIELD_NAMES = new Set(['useValue', 'useFactory', 'data']);
export class LowerMetadataTransform implements RequestsMap, MetadataTransformer {
private cache: MetadataCache;
private requests = new Map<string, RequestLocationMap>();
private lowerableFieldNames: Set<string>;
constructor(lowerableFieldNames: string[]) {
this.lowerableFieldNames = new Set<string>(lowerableFieldNames);
}
// RequestMap
getRequests(sourceFile: ts.SourceFile): RequestLocationMap {
@ -312,17 +315,46 @@ export class LowerMetadataTransform implements RequestsMap, MetadataTransformer
return false;
};
const hasLowerableParentCache = new Map<ts.Node, boolean>();
const shouldBeLowered = (node: ts.Node | undefined): boolean => {
if (node === undefined) {
return false;
}
let lowerable: boolean = false;
if ((node.kind === ts.SyntaxKind.ArrowFunction ||
node.kind === ts.SyntaxKind.FunctionExpression) &&
isEligibleForLowering(node)) {
lowerable = true;
} else if (
isLiteralFieldNamed(node, this.lowerableFieldNames) && isEligibleForLowering(node) &&
!isExportedSymbol(node) && !isExportedPropertyAccess(node)) {
lowerable = true;
}
return lowerable;
};
const hasLowerableParent = (node: ts.Node | undefined): boolean => {
if (node === undefined) {
return false;
}
if (!hasLowerableParentCache.has(node)) {
hasLowerableParentCache.set(
node, shouldBeLowered(node.parent) || hasLowerableParent(node.parent));
}
return hasLowerableParentCache.get(node) !;
};
const isLowerable = (node: ts.Node | undefined): boolean => {
if (node === undefined) {
return false;
}
return shouldBeLowered(node) && !hasLowerableParent(node);
};
return (value: MetadataValue, node: ts.Node): MetadataValue => {
if (!isPrimitive(value) && !isRewritten(value)) {
if ((node.kind === ts.SyntaxKind.ArrowFunction ||
node.kind === ts.SyntaxKind.FunctionExpression) &&
shouldLower(node)) {
return replaceNode(node);
}
if (isLiteralFieldNamed(node, LOWERABLE_FIELD_NAMES) && shouldLower(node) &&
!isExportedSymbol(node) && !isExportedPropertyAccess(node)) {
return replaceNode(node);
}
if (!isPrimitive(value) && !isRewritten(value) && isLowerable(node)) {
return replaceNode(node);
}
return value;
};

View File

@ -7,7 +7,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, FormattedMessageChain, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedFileWithInjectables, NgAnalyzedModules, ParseSourceSpan, PartialModule, Position, Serializer, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isFormattedError, isSyntaxError} from '@angular/compiler';
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, FormattedMessageChain, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedFileWithInjectables, NgAnalyzedModules, ParseSourceSpan, PartialModule, Position, Serializer, StaticSymbol, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isFormattedError, isSyntaxError} from '@angular/compiler';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
@ -22,9 +22,11 @@ import {LowerMetadataTransform, getExpressionLoweringTransformFactory} from './l
import {MetadataCache, MetadataTransformer} from './metadata_cache';
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
import {PartialModuleMetadataTransformer} from './r3_metadata_transform';
import {StripDecoratorsMetadataTransformer, getDecoratorStripTransformerFactory} from './r3_strip_decorators';
import {getAngularClassTransformerFactory} from './r3_transform';
import {DTS, GENERATED_FILES, StructureIsReused, TS, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util';
// Closure compiler transforms the form `Service.ngInjectableDef = X` into
// `Service$ngInjectableDef = X`. To prevent this transformation, such assignments need to be
// annotated with @nocollapse. Unfortunately, a bug in Typescript where comments aren't propagated
@ -62,6 +64,25 @@ const R3_NOCOLLAPSE_DEFS = '$1\/** @nocollapse *\/ $2';
*/
const MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT = 20;
/**
* Fields to lower within metadata in render2 mode.
*/
const LOWER_FIELDS = ['useValue', 'useFactory', 'data'];
/**
* Fields to lower within metadata in render3 mode.
*/
const R3_LOWER_FIELDS = [...LOWER_FIELDS, 'providers', 'imports', 'exports'];
const R3_REIFIED_DECORATORS = [
'Component',
'Directive',
'Injectable',
'NgModule',
'Pipe',
];
const emptyModules: NgAnalyzedModules = {
ngModules: [],
ngModuleByPipeOrDirective: new Map(),
@ -96,6 +117,7 @@ class AngularCompilerProgram implements Program {
private _structuralDiagnostics: Diagnostic[]|undefined;
private _programWithStubs: ts.Program|undefined;
private _optionsDiagnostics: Diagnostic[] = [];
private _reifiedDecorators: Set<StaticSymbol>;
constructor(
rootNames: ReadonlyArray<string>, private options: CompilerOptions,
@ -129,7 +151,9 @@ class AngularCompilerProgram implements Program {
this.host = bundleHost;
}
}
this.loweringMetadataTransform = new LowerMetadataTransform();
this.loweringMetadataTransform =
new LowerMetadataTransform(options.enableIvy ? R3_LOWER_FIELDS : LOWER_FIELDS);
this.metadataCache = this.createMetadataCache([this.loweringMetadataTransform]);
}
@ -269,7 +293,11 @@ class AngularCompilerProgram implements Program {
0) {
return {emitSkipped: true, diagnostics: [], emittedFiles: []};
}
const modules = this.compiler.emitAllPartialModules(this.analyzedModules);
// analyzedModules and analyzedInjectables are created together. If one exists, so does the
// other.
const modules =
this.compiler.emitAllPartialModules(this.analyzedModules, this._analyzedInjectables !);
const writeTsFile: ts.WriteFileCallback =
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
@ -285,7 +313,8 @@ class AngularCompilerProgram implements Program {
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
const tsCustomTransformers = this.calculateTransforms(
/* genFiles */ undefined, /* partialModules */ modules, customTransformers);
/* genFiles */ undefined, /* partialModules */ modules,
/* stripDecorators */ this.reifiedDecorators, customTransformers);
const emitResult = emitCallback({
program: this.tsProgram,
@ -356,8 +385,8 @@ class AngularCompilerProgram implements Program {
const modules = this._analyzedInjectables &&
this.compiler.emitAllPartialModules2(this._analyzedInjectables);
const tsCustomTransformers =
this.calculateTransforms(genFileByFileName, modules, customTransformers);
const tsCustomTransformers = this.calculateTransforms(
genFileByFileName, modules, /* stripDecorators */ undefined, customTransformers);
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
// Restore the original references before we emit so TypeScript doesn't emit
// a reference to the .d.ts file.
@ -512,8 +541,18 @@ class AngularCompilerProgram implements Program {
return this._tsProgram !;
}
private get reifiedDecorators(): Set<StaticSymbol> {
if (!this._reifiedDecorators) {
const reflector = this.compiler.reflector;
this._reifiedDecorators = new Set(
R3_REIFIED_DECORATORS.map(name => reflector.findDeclaration('@angular/core', name)));
}
return this._reifiedDecorators;
}
private calculateTransforms(
genFiles: Map<string, GeneratedFile>|undefined, partialModules: PartialModule[]|undefined,
stripDecorators: Set<StaticSymbol>|undefined,
customTransformers?: CustomTransformers): ts.CustomTransformers {
const beforeTs: Array<ts.TransformerFactory<ts.SourceFile>> = [];
const metadataTransforms: MetadataTransformer[] = [];
@ -521,6 +560,7 @@ class AngularCompilerProgram implements Program {
beforeTs.push(getInlineResourcesTransformFactory(this.tsProgram, this.hostAdapter));
metadataTransforms.push(new InlineResourcesMetadataTransformer(this.hostAdapter));
}
if (!this.options.disableExpressionLowering) {
beforeTs.push(
getExpressionLoweringTransformFactory(this.loweringMetadataTransform, this.tsProgram));
@ -536,6 +576,14 @@ class AngularCompilerProgram implements Program {
// the partial module transforms.
metadataTransforms.push(new PartialModuleMetadataTransformer(partialModules));
}
if (stripDecorators) {
beforeTs.push(getDecoratorStripTransformerFactory(
stripDecorators, this.compiler.reflector, this.getTsProgram().getTypeChecker()));
metadataTransforms.push(
new StripDecoratorsMetadataTransformer(stripDecorators, this.compiler.reflector));
}
if (customTransformers && customTransformers.beforeTs) {
beforeTs.push(...customTransformers.beforeTs);
}
@ -832,6 +880,7 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
preserveWhitespaces: options.preserveWhitespaces,
fullTemplateTypeCheck: options.fullTemplateTypeCheck,
allowEmptyCodegenFiles: options.allowEmptyCodegenFiles,
enableIvy: options.enableIvy,
};
}

View File

@ -0,0 +1,161 @@
/**
* @license
* Copyright Google Inc. 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 {StaticReflector, StaticSymbol} from '@angular/compiler';
import * as ts from 'typescript';
import {MetadataValue, isClassMetadata, isMetadataImportedSymbolReferenceExpression, isMetadataSymbolicCallExpression} from '../metadata';
import {MetadataTransformer, ValueTransform} from './metadata_cache';
export type Transformer = (sourceFile: ts.SourceFile) => ts.SourceFile;
export type TransformerFactory = (context: ts.TransformationContext) => Transformer;
export function getDecoratorStripTransformerFactory(
coreDecorators: Set<StaticSymbol>, reflector: StaticReflector,
checker: ts.TypeChecker): TransformerFactory {
return function(context: ts.TransformationContext) {
return function(sourceFile: ts.SourceFile): ts.SourceFile {
const stripDecoratorsFromClassDeclaration =
(node: ts.ClassDeclaration): ts.ClassDeclaration => {
if (node.decorators === undefined) {
return node;
}
const decorators = node.decorators.filter(decorator => {
const callExpr = decorator.expression;
if (ts.isCallExpression(callExpr)) {
const id = callExpr.expression;
if (ts.isIdentifier(id)) {
const symbol = resolveToStaticSymbol(id, sourceFile.fileName, reflector, checker);
return symbol && coreDecorators.has(symbol);
}
}
return true;
});
if (decorators.length !== node.decorators.length) {
return ts.updateClassDeclaration(
node, decorators, node.modifiers, node.name, node.typeParameters,
node.heritageClauses || [], node.members, );
}
return node;
};
const stripDecoratorPropertyAssignment = (node: ts.ClassDeclaration): ts.ClassDeclaration => {
return ts.visitEachChild(node, member => {
if (!ts.isPropertyDeclaration(member) || !isDecoratorAssignment(member) ||
!member.initializer || !ts.isArrayLiteralExpression(member.initializer)) {
return member;
}
const newInitializer = ts.visitEachChild(member.initializer, decorator => {
if (!ts.isObjectLiteralExpression(decorator)) {
return decorator;
}
const type = lookupProperty(decorator, 'type');
if (!type || !ts.isIdentifier(type)) {
return decorator;
}
const symbol = resolveToStaticSymbol(type, sourceFile.fileName, reflector, checker);
if (!symbol || !coreDecorators.has(symbol)) {
return decorator;
}
return undefined;
}, context);
if (newInitializer === member.initializer) {
return member;
} else if (newInitializer.elements.length === 0) {
return undefined;
} else {
return ts.updateProperty(
member, member.decorators, member.modifiers, member.name, member.questionToken,
member.type, newInitializer);
}
}, context);
};
return ts.visitEachChild(sourceFile, stmt => {
if (ts.isClassDeclaration(stmt)) {
let decl = stmt;
if (stmt.decorators) {
decl = stripDecoratorsFromClassDeclaration(stmt);
}
return stripDecoratorPropertyAssignment(decl);
}
return stmt;
}, context);
};
};
}
function isDecoratorAssignment(member: ts.ClassElement): boolean {
if (!ts.isPropertyDeclaration(member)) {
return false;
}
if (!member.modifiers ||
!member.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword)) {
return false;
}
if (!ts.isIdentifier(member.name) || member.name.text !== 'decorators') {
return false;
}
if (!member.initializer || !ts.isArrayLiteralExpression(member.initializer)) {
return false;
}
return true;
}
function lookupProperty(expr: ts.ObjectLiteralExpression, prop: string): ts.Expression|undefined {
const decl = expr.properties.find(
elem => !!elem.name && ts.isIdentifier(elem.name) && elem.name.text === prop);
if (decl === undefined || !ts.isPropertyAssignment(decl)) {
return undefined;
}
return decl.initializer;
}
function resolveToStaticSymbol(
id: ts.Identifier, containingFile: string, reflector: StaticReflector,
checker: ts.TypeChecker): StaticSymbol|null {
const res = checker.getSymbolAtLocation(id);
if (!res || !res.declarations || res.declarations.length === 0) {
return null;
}
const decl = res.declarations[0];
if (!ts.isImportSpecifier(decl)) {
return null;
}
const moduleSpecifier = decl.parent !.parent !.parent !.moduleSpecifier;
if (!ts.isStringLiteral(moduleSpecifier)) {
return null;
}
return reflector.tryFindDeclaration(moduleSpecifier.text, id.text, containingFile);
}
export class StripDecoratorsMetadataTransformer implements MetadataTransformer {
constructor(private coreDecorators: Set<StaticSymbol>, private reflector: StaticReflector) {}
start(sourceFile: ts.SourceFile): ValueTransform|undefined {
return (value: MetadataValue, node: ts.Node): MetadataValue => {
if (isClassMetadata(value) && ts.isClassDeclaration(node) && value.decorators) {
value.decorators = value.decorators.filter(d => {
if (isMetadataSymbolicCallExpression(d) &&
isMetadataImportedSymbolReferenceExpression(d.expression)) {
const declaration = this.reflector.tryFindDeclaration(
d.expression.module, d.expression.name, sourceFile.fileName);
if (declaration && this.coreDecorators.has(declaration)) {
return false;
}
}
return true;
});
}
return value;
};
}
}

View File

@ -2205,4 +2205,120 @@ describe('ngc transformer command-line', () => {
expect(source).toMatch(/new Service\(i0\.inject\(exports\.TOKEN\)\);/);
});
});
describe('ngInjectorDef', () => {
it('is applied with lowered metadata', () => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["module.ts"],
"angularCompilerOptions": {
"enableIvy": true,
"skipTemplateCodegen": true
}
}`);
write('module.ts', `
import {Injectable, NgModule} from '@angular/core';
@Injectable()
export class ServiceA {}
@Injectable()
export class ServiceB {}
@NgModule()
export class Exported {}
@NgModule({
providers: [ServiceA]
})
export class Imported {
static forRoot() {
console.log('not statically analyzable');
return {
ngModule: Imported,
providers: [] as any,
};
}
}
@NgModule({
providers: [ServiceA, ServiceB],
imports: [Imported.forRoot()],
exports: [Exported],
})
export class Module {}
`);
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
const modulePath = path.resolve(outDir, 'module.js');
const moduleSource = fs.readFileSync(modulePath, 'utf8');
expect(moduleSource)
.toContain('var ɵ1 = [ServiceA, ServiceB], ɵ2 = [Imported.forRoot()], ɵ3 = [Exported];');
expect(moduleSource)
.toContain(
'Imported.ngInjectorDef = i0.defineInjector({ factory: function Imported_Factory() { return new Imported(); }, providers: ɵ0, imports: [] });');
expect(moduleSource)
.toContain(
'Module.ngInjectorDef = i0.defineInjector({ factory: function Module_Factory() { return new Module(); }, providers: ɵ1, imports: [ɵ2, ɵ3] });');
});
it('strips decorator in ivy mode', () => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["service.ts"],
"angularCompilerOptions": {
"enableIvy": true
}
}`);
write('service.ts', `
import {Injectable, Self} from '@angular/core';
@Injectable()
export class ServiceA {}
@Injectable()
@Self()
export class ServiceB {}
`);
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
const modulePath = path.resolve(outDir, 'service.js');
const moduleSource = fs.readFileSync(modulePath, 'utf8');
expect(moduleSource).not.toMatch(/ServiceA\.decorators =/);
expect(moduleSource).toMatch(/ServiceB\.decorators =/);
expect(moduleSource).toMatch(/type: Self/);
expect(moduleSource).not.toMatch(/type: Injectable/);
});
it('rewrites Injector to INJECTOR in Ivy factory functions ', () => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["service.ts"],
"angularCompilerOptions": {
"enableIvy": true
}
}`);
write('service.ts', `
import {Injectable, Injector} from '@angular/core';
@Injectable()
export class Service {
constructor(private injector: Injector) {}
}
`);
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
const modulePath = path.resolve(outDir, 'service.js');
const moduleSource = fs.readFileSync(modulePath, 'utf8');
expect(moduleSource).not.toMatch(/inject\(i0\.Injector/);
expect(moduleSource).toMatch(/inject\(i0\.INJECTOR/);
});
});
});

View File

@ -13,6 +13,8 @@ import {LowerMetadataTransform, LoweringRequest, RequestLocationMap, getExpressi
import {MetadataCache} from '../../src/transformers/metadata_cache';
import {Directory, MockAotContext, MockCompilerHost} from '../mocks';
const DEFAULT_FIELDS_TO_LOWER = ['useFactory', 'useValue', 'data'];
describe('Expression lowering', () => {
describe('transform', () => {
it('should be able to lower a simple expression', () => {
@ -112,7 +114,8 @@ describe('Expression lowering', () => {
it('should throw a validation exception for invalid files', () => {
const cache = new MetadataCache(
new MetadataCollector({}), /* strict */ true, [new LowerMetadataTransform()]);
new MetadataCollector({}), /* strict */ true,
[new LowerMetadataTransform(DEFAULT_FIELDS_TO_LOWER)]);
const sourceFile = ts.createSourceFile(
'foo.ts', `
import {Injectable} from '@angular/core';
@ -129,7 +132,8 @@ describe('Expression lowering', () => {
it('should not report validation errors on a .d.ts file', () => {
const cache = new MetadataCache(
new MetadataCollector({}), /* strict */ true, [new LowerMetadataTransform()]);
new MetadataCollector({}), /* strict */ true,
[new LowerMetadataTransform(DEFAULT_FIELDS_TO_LOWER)]);
const dtsFile = ts.createSourceFile(
'foo.d.ts', `
import {Injectable} from '@angular/core';
@ -244,7 +248,7 @@ function normalizeResult(result: string): string {
function collect(annotatedSource: string) {
const {annotations, unannotatedSource} = getAnnotations(annotatedSource);
const transformer = new LowerMetadataTransform();
const transformer = new LowerMetadataTransform(DEFAULT_FIELDS_TO_LOWER);
const cache = new MetadataCache(new MetadataCollector({}), false, [transformer]);
const sourceFile = ts.createSourceFile(
'someName.ts', unannotatedSource, ts.ScriptTarget.Latest, /* setParentNodes */ true);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileShallowModuleMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {ConstantPool} from '../constant_pool';
import {ViewEncapsulation} from '../core';
@ -20,6 +20,7 @@ import {NgModuleCompiler} from '../ng_module_compiler';
import {OutputEmitter} from '../output/abstract_emitter';
import * as o from '../output/output_ast';
import {ParseError} from '../parse_util';
import {compileNgModule as compileIvyModule} from '../render3/r3_module_compiler';
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
import {OutputMode} from '../render3/r3_types';
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
@ -57,7 +58,7 @@ export class AotCompiler {
constructor(
private _config: CompilerConfig, private _options: AotCompilerOptions,
private _host: AotCompilerHost, private _reflector: StaticReflector,
private _host: AotCompilerHost, readonly reflector: StaticReflector,
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _typeCheckCompiler: TypeCheckCompiler, private _ngModuleCompiler: NgModuleCompiler,
@ -283,7 +284,7 @@ export class AotCompiler {
private _externalIdentifierReferences(references: o.ExternalReference[]): StaticSymbol[] {
const result: StaticSymbol[] = [];
for (let reference of references) {
const token = createTokenForExternalReference(this._reflector, reference);
const token = createTokenForExternalReference(this.reflector, reference);
if (token.identifier) {
result.push(token.identifier.reference);
}
@ -332,28 +333,49 @@ export class AotCompiler {
return messageBundle;
}
emitAllPartialModules({ngModuleByPipeOrDirective, files}: NgAnalyzedModules): PartialModule[] {
// Using reduce like this is a select many pattern (where map is a select pattern)
return files.reduce<PartialModule[]>((r, file) => {
r.push(...this._emitPartialModule(
file.fileName, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules,
file.injectables));
return r;
}, []);
emitAllPartialModules(
{ngModuleByPipeOrDirective, files}: NgAnalyzedModules,
r3Files: NgAnalyzedFileWithInjectables[]): PartialModule[] {
const contextMap = new Map<string, OutputContext>();
const getContext = (fileName: string): OutputContext => {
if (!contextMap.has(fileName)) {
contextMap.set(fileName, this._createOutputContext(fileName));
}
return contextMap.get(fileName) !;
};
files.forEach(
file => this._compilePartialModule(
file.fileName, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules,
file.injectables, getContext(file.fileName)));
r3Files.forEach(
file => this._compileShallowModules(
file.fileName, file.shallowModules, getContext(file.fileName)));
return Array.from(contextMap.values())
.map(context => ({
fileName: context.genFilePath,
statements: [...context.constantPool.statements, ...context.statements],
}));
}
private _emitPartialModule(
private _compileShallowModules(
fileName: string, shallowModules: CompileShallowModuleMetadata[],
context: OutputContext): void {
shallowModules.forEach(module => compileIvyModule(context, module, this._injectableCompiler));
}
private _compilePartialModule(
fileName: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
injectables: CompileInjectableMetadata[]): PartialModule[] {
injectables: CompileInjectableMetadata[], context: OutputContext): void {
const classes: o.ClassStmt[] = [];
const errors: ParseError[] = [];
const context = this._createOutputContext(fileName);
const hostBindingParser = new BindingParser(
this._templateParser.expressionParser, DEFAULT_INTERPOLATION_CONFIG, null !, [], errors);
// Process all components and directives
directives.forEach(directiveType => {
const directiveMetadata = this._metadataResolver.getDirectiveMetadata(directiveType);
@ -366,28 +388,22 @@ export class AotCompiler {
const {template: parsedTemplate, pipes: parsedPipes} =
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
compileIvyComponent(
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector,
context, directiveMetadata, parsedPipes, parsedTemplate, this.reflector,
hostBindingParser, OutputMode.PartialClass);
} else {
compileIvyDirective(
context, directiveMetadata, this._reflector, hostBindingParser,
OutputMode.PartialClass);
context, directiveMetadata, this.reflector, hostBindingParser, OutputMode.PartialClass);
}
});
pipes.forEach(pipeType => {
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
if (pipeMetadata) {
compileIvyPipe(context, pipeMetadata, this._reflector, OutputMode.PartialClass);
compileIvyPipe(context, pipeMetadata, this.reflector, OutputMode.PartialClass);
}
});
injectables.forEach(injectable => this._injectableCompiler.compile(injectable, context));
if (context.statements && context.statements.length > 0) {
return [{fileName, statements: [...context.constantPool.statements, ...context.statements]}];
}
return [];
}
emitAllPartialModules2(files: NgAnalyzedFileWithInjectables[]): PartialModule[] {
@ -531,14 +547,14 @@ export class AotCompiler {
if (this._options.locale) {
const normalizedLocale = this._options.locale.replace(/_/g, '-');
providers.push({
token: createTokenForExternalReference(this._reflector, Identifiers.LOCALE_ID),
token: createTokenForExternalReference(this.reflector, Identifiers.LOCALE_ID),
useValue: normalizedLocale,
});
}
if (this._options.i18nFormat) {
providers.push({
token: createTokenForExternalReference(this._reflector, Identifiers.TRANSLATIONS_FORMAT),
token: createTokenForExternalReference(this.reflector, Identifiers.TRANSLATIONS_FORMAT),
useValue: this._options.i18nFormat
});
}
@ -682,12 +698,12 @@ export class AotCompiler {
listLazyRoutes(entryRoute?: string, analyzedModules?: NgAnalyzedModules): LazyRoute[] {
const self = this;
if (entryRoute) {
const symbol = parseLazyRoute(entryRoute, this._reflector).referencedModule;
const symbol = parseLazyRoute(entryRoute, this.reflector).referencedModule;
return visitLazyRoute(symbol);
} else if (analyzedModules) {
const allLazyRoutes: LazyRoute[] = [];
for (const ngModule of analyzedModules.ngModules) {
const lazyRoutes = listLazyRoutes(ngModule, this._reflector);
const lazyRoutes = listLazyRoutes(ngModule, this.reflector);
for (const lazyRoute of lazyRoutes) {
allLazyRoutes.push(lazyRoute);
}
@ -707,7 +723,7 @@ export class AotCompiler {
}
seenRoutes.add(symbol);
const lazyRoutes = listLazyRoutes(
self._metadataResolver.getNgModuleMetadata(symbol, true) !, self._reflector);
self._metadataResolver.getNgModuleMetadata(symbol, true) !, self.reflector);
for (const lazyRoute of lazyRoutes) {
allLazyRoutes.push(lazyRoute);
visitLazyRoute(lazyRoute.referencedModule, seenRoutes, allLazyRoutes);
@ -748,6 +764,7 @@ export interface NgAnalyzedModules {
export interface NgAnalyzedFileWithInjectables {
fileName: string;
injectables: CompileInjectableMetadata[];
shallowModules: CompileShallowModuleMetadata[];
}
export interface NgAnalyzedFile {
@ -868,6 +885,7 @@ export function analyzeFileForInjectables(
host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver,
metadataResolver: CompileMetadataResolver, fileName: string): NgAnalyzedFileWithInjectables {
const injectables: CompileInjectableMetadata[] = [];
const shallowModules: CompileShallowModuleMetadata[] = [];
if (staticSymbolResolver.hasDecorators(fileName)) {
staticSymbolResolver.getSymbolsOf(fileName).forEach((symbol) => {
const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol);
@ -883,11 +901,17 @@ export function analyzeFileForInjectables(
if (injectable) {
injectables.push(injectable);
}
} else if (metadataResolver.isNgModule(symbol)) {
isNgSymbol = true;
const module = metadataResolver.getShallowModuleMetadata(symbol);
if (module) {
shallowModules.push(module);
}
}
}
});
}
return {fileName, injectables};
return {fileName, injectables, shallowModules};
}
function isValueExportingNonSourceFile(host: NgAnalyzeModulesHost, metadata: any): boolean {

View File

@ -90,7 +90,8 @@ export function createAotCompiler(
const compiler = new AotCompiler(
config, options, compilerHost, staticReflector, resolver, tmplParser,
new StyleCompiler(urlResolver), viewCompiler, typeCheckCompiler,
new NgModuleCompiler(staticReflector), new InjectableCompiler(staticReflector),
new TypeScriptEmitter(), summaryResolver, symbolResolver);
new NgModuleCompiler(staticReflector),
new InjectableCompiler(staticReflector, !!options.enableIvy), new TypeScriptEmitter(),
summaryResolver, symbolResolver);
return {compiler, reflector: staticReflector};
}

View File

@ -18,4 +18,5 @@ export interface AotCompilerOptions {
fullTemplateTypeCheck?: boolean;
allowEmptyCodegenFiles?: boolean;
strictInjectionParameters?: boolean;
enableIvy?: boolean;
}

View File

@ -42,6 +42,7 @@ function shouldIgnore(value: any): boolean {
*/
export class StaticReflector implements CompileReflector {
private annotationCache = new Map<StaticSymbol, any[]>();
private shallowAnnotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
private parameterCache = new Map<StaticSymbol, any[]>();
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
@ -106,8 +107,9 @@ export class StaticReflector implements CompileReflector {
this.symbolResolver.getSymbolByModule(moduleUrl, name, containingFile));
}
tryFindDeclaration(moduleUrl: string, name: string): StaticSymbol {
return this.symbolResolver.ignoreErrorsFor(() => this.findDeclaration(moduleUrl, name));
tryFindDeclaration(moduleUrl: string, name: string, containingFile?: string): StaticSymbol {
return this.symbolResolver.ignoreErrorsFor(
() => this.findDeclaration(moduleUrl, name, containingFile));
}
findSymbolDeclaration(symbol: StaticSymbol): StaticSymbol {
@ -135,7 +137,21 @@ export class StaticReflector implements CompileReflector {
}
public annotations(type: StaticSymbol): any[] {
let annotations = this.annotationCache.get(type);
return this._annotations(
type, (type: StaticSymbol, decorators: any) => this.simplify(type, decorators),
this.annotationCache);
}
public shallowAnnotations(type: StaticSymbol): any[] {
return this._annotations(
type, (type: StaticSymbol, decorators: any) => this.simplify(type, decorators, true),
this.shallowAnnotationCache);
}
private _annotations(
type: StaticSymbol, simplify: (type: StaticSymbol, decorators: any) => any,
annotationCache: Map<StaticSymbol, any[]>): any[] {
let annotations = annotationCache.get(type);
if (!annotations) {
annotations = [];
const classMetadata = this.getTypeMetadata(type);
@ -146,7 +162,7 @@ export class StaticReflector implements CompileReflector {
}
let ownAnnotations: any[] = [];
if (classMetadata['decorators']) {
ownAnnotations = this.simplify(type, classMetadata['decorators']);
ownAnnotations = simplify(type, classMetadata['decorators']);
annotations.push(...ownAnnotations);
}
if (parentType && !this.summaryResolver.isLibraryFile(type.filePath) &&
@ -169,7 +185,7 @@ export class StaticReflector implements CompileReflector {
}
}
}
this.annotationCache.set(type, annotations.filter(ann => !!ann));
annotationCache.set(type, annotations.filter(ann => !!ann));
}
return annotations;
}
@ -414,7 +430,7 @@ export class StaticReflector implements CompileReflector {
}
/** @internal */
public simplify(context: StaticSymbol, value: any): any {
public simplify(context: StaticSymbol, value: any, lazy: boolean = false): any {
const self = this;
let scope = BindingScope.empty;
const calling = new Map<StaticSymbol, boolean>();
@ -775,7 +791,7 @@ export class StaticReflector implements CompileReflector {
let result: any;
try {
result = simplifyInContext(context, value, 0, 0);
result = simplifyInContext(context, value, 0, lazy ? 1 : 0);
} catch (e) {
if (this.errorRecorder) {
this.reportError(e, context);

View File

@ -527,6 +527,14 @@ export interface CompileNgModuleSummary extends CompileTypeSummary {
modules: CompileTypeMetadata[];
}
export class CompileShallowModuleMetadata {
type: CompileTypeMetadata;
rawExports: any;
rawImports: any;
rawProviders: any;
}
/**
* Metadata regarding compilation of a module.
*/

View File

@ -15,6 +15,7 @@ import * as o from './output/output_ast';
export abstract class CompileReflector {
abstract parameters(typeOrFunc: /*Type*/ any): any[][];
abstract annotations(typeOrFunc: /*Type*/ any): any[];
abstract shallowAnnotations(typeOrFunc: /*Type*/ any): any[];
abstract tryAnnotations(typeOrFunc: /*Type*/ any): any[];
abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]};
abstract hasLifecycleHook(type: any, lcProperty: string): boolean;

View File

@ -62,6 +62,7 @@ export class Identifiers {
};
static inject: o.ExternalReference = {name: 'inject', 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 ViewEncapsulation: o.ExternalReference = {
@ -88,7 +89,6 @@ export class Identifiers {
static inlineInterpolate: o.ExternalReference = {
name: 'ɵinlineInterpolate',
moduleName: CORE,
};
static interpolate: o.ExternalReference = {name: 'ɵinterpolate', moduleName: CORE};
static EMPTY_ARRAY: o.ExternalReference = {name: 'ɵEMPTY_ARRAY', moduleName: CORE};

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol} from './aot/static_symbol';
import {CompileInjectableMetadata, CompileNgModuleMetadata, CompileProviderMetadata, identifierName} from './compile_metadata';
import {CompileReflector} from './compile_reflector';
import {InjectFlags, NodeFlags} from './core';
@ -29,7 +30,10 @@ function mapEntry(key: string, value: o.Expression): MapEntry {
}
export class InjectableCompiler {
constructor(private reflector: CompileReflector) {}
private tokenInjector: StaticSymbol;
constructor(private reflector: CompileReflector, private alwaysGenerateDef: boolean) {
this.tokenInjector = reflector.resolveExternalReference(Identifiers.Injector);
}
private depsArray(deps: any[], ctx: OutputContext): o.Expression[] {
return deps.map(dep => {
@ -55,7 +59,16 @@ export class InjectableCompiler {
}
}
}
const tokenExpr = typeof token === 'string' ? o.literal(token) : ctx.importExpr(token);
let tokenExpr: o.Expression;
if (typeof token === 'string') {
tokenExpr = o.literal(token);
} else if (token === this.tokenInjector && this.alwaysGenerateDef) {
tokenExpr = o.importExpr(Identifiers.INJECTOR);
} else {
tokenExpr = ctx.importExpr(token);
}
if (flags !== InjectFlags.Default || defaultValue !== undefined) {
args = [tokenExpr, o.literal(defaultValue), o.literal(flags)];
} else {
@ -65,7 +78,7 @@ export class InjectableCompiler {
});
}
private factoryFor(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression {
factoryFor(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression {
let retValue: o.Expression;
if (injectable.useExisting) {
retValue = o.importExpr(Identifiers.inject).callFn([ctx.importExpr(injectable.useExisting)]);
@ -90,8 +103,10 @@ export class InjectableCompiler {
injectableDef(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression {
let providedIn: o.Expression = o.NULL_EXPR;
if (injectable.providedIn) {
if (typeof injectable.providedIn === 'string') {
if (injectable.providedIn !== undefined) {
if (injectable.providedIn === null) {
providedIn = o.NULL_EXPR;
} else if (typeof injectable.providedIn === 'string') {
providedIn = o.literal(injectable.providedIn);
} else {
providedIn = ctx.importExpr(injectable.providedIn);
@ -106,7 +121,7 @@ export class InjectableCompiler {
}
compile(injectable: CompileInjectableMetadata, ctx: OutputContext): void {
if (injectable.providedIn) {
if (this.alwaysGenerateDef || injectable.providedIn !== undefined) {
const className = identifierName(injectable.type) !;
const clazz = new o.ClassStmt(
className, null,

View File

@ -12,9 +12,9 @@ import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata';
import {CompileReflector} from './compile_reflector';
import {CompilerConfig} from './config';
import {ChangeDetectionStrategy, Component, Directive, Injectable, ModuleWithProviders, Provider, Query, SchemaMetadata, Type, ViewEncapsulation, createAttribute, createComponent, createHost, createInject, createInjectable, createInjectionToken, createOptional, createSelf, createSkipSelf} from './core';
import {ChangeDetectionStrategy, Component, Directive, Injectable, ModuleWithProviders, Provider, Query, SchemaMetadata, Type, ViewEncapsulation, createAttribute, createComponent, createHost, createInject, createInjectable, createInjectionToken, createNgModule, createOptional, createSelf, createSkipSelf} from './core';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveResolver} from './directive_resolver';
import {DirectiveResolver, findLast} from './directive_resolver';
import {Identifiers} from './identifiers';
import {getAllLifecycleHooks} from './lifecycle_reflector';
import {HtmlParser} from './ml_parser/html_parser';
@ -44,6 +44,7 @@ export class CompileMetadataResolver {
private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>();
private _ngModuleCache = new Map<Type, cpl.CompileNgModuleMetadata>();
private _ngModuleOfTypes = new Map<Type, Type>();
private _shallowModuleCache = new Map<Type, cpl.CompileShallowModuleMetadata>();
constructor(
private _config: CompilerConfig, private _htmlParser: HtmlParser,
@ -477,6 +478,26 @@ export class CompileMetadataResolver {
return Promise.all(loading);
}
getShallowModuleMetadata(moduleType: any): cpl.CompileShallowModuleMetadata|null {
let compileMeta = this._shallowModuleCache.get(moduleType);
if (compileMeta) {
return compileMeta;
}
const ngModuleMeta =
findLast(this._reflector.shallowAnnotations(moduleType), createNgModule.isTypeOf);
compileMeta = {
type: this._getTypeMetadata(moduleType),
rawExports: ngModuleMeta.exports,
rawImports: ngModuleMeta.imports,
rawProviders: ngModuleMeta.providers,
};
this._shallowModuleCache.set(moduleType, compileMeta);
return compileMeta;
}
getNgModuleMetadata(
moduleType: any, throwIfNotFound = true,
alreadyCollecting: Set<any>|null = null): cpl.CompileNgModuleMetadata|null {

View File

@ -0,0 +1,29 @@
/**
* @license
* Copyright Google Inc. 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 * as o from './output_ast';
export type MapEntry = {
key: string,
quoted: boolean,
value: o.Expression
};
export type MapLiteral = MapEntry[];
export function mapEntry(key: string, value: o.Expression): MapEntry {
return {key, value, quoted: false};
}
export function mapLiteral(obj: {[key: string]: o.Expression}): o.Expression {
return o.literalMap(Object.keys(obj).map(key => ({
key,
quoted: false,
value: obj[key],
})));
}

View File

@ -102,6 +102,11 @@ export class Identifiers {
moduleName: CORE,
};
static defineInjector: o.ExternalReference = {
name: 'defineInjector',
moduleName: CORE,
};
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
static query: o.ExternalReference = {name: 'ɵQ', moduleName: CORE};

View File

@ -0,0 +1,60 @@
/**
* @license
* Copyright Google Inc. 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 {StaticSymbol} from '../aot/static_symbol';
import {CompileShallowModuleMetadata, identifierName} from '../compile_metadata';
import {InjectableCompiler} from '../injectable_compiler';
import {mapLiteral} from '../output/map_util';
import * as o from '../output/output_ast';
import {OutputContext} from '../util';
import {Identifiers as R3} from './r3_identifiers';
const EMPTY_ARRAY = o.literalArr([]);
function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression {
if (Array.isArray(meta)) {
return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx)));
} else if (meta instanceof StaticSymbol) {
return ctx.importExpr(meta);
} else if (meta == null) {
return o.literal(meta);
} else {
throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`);
}
}
export function compileNgModule(
ctx: OutputContext, ngModule: CompileShallowModuleMetadata,
injectableCompiler: InjectableCompiler): void {
const className = identifierName(ngModule.type) !;
const rawImports = ngModule.rawImports ? [ngModule.rawImports] : [];
const rawExports = ngModule.rawExports ? [ngModule.rawExports] : [];
const injectorDefArg = mapLiteral({
'factory':
injectableCompiler.factoryFor({type: ngModule.type, symbol: ngModule.type.reference}, ctx),
'providers': convertMetaToOutput(ngModule.rawProviders, ctx),
'imports': convertMetaToOutput([...rawImports, ...rawExports], ctx),
});
const injectorDef = o.importExpr(R3.defineInjector).callFn([injectorDefArg]);
ctx.statements.push(new o.ClassStmt(
/* name */ className,
/* parent */ null,
/* fields */[new o.ClassField(
/* name */ 'ngInjectorDef',
/* type */ o.INFERRED_TYPE,
/* modifiers */[o.StmtModifier.Static],
/* initializer */ injectorDef, )],
/* getters */[],
/* constructorMethod */ new o.ClassMethod(null, [], []),
/* methods */[]));
}

View File

@ -13,13 +13,13 @@
*/
export * from './di/metadata';
export {defineInjectable, Injectable, InjectableDecorator, InjectableProvider, InjectableType} from './di/injectable';
export * from './di/defs';
export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
export {inject, InjectFlags, Injector} from './di/injector';
export {Injectable, InjectableDecorator, InjectableProvider} from './di/injectable';
export {inject, InjectFlags, INJECTOR, Injector} from './di/injector';
export {ReflectiveInjector} from './di/reflective_injector';
export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider';
export {createInjector} from './di/r3_injector';
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
export {ReflectiveKey} from './di/reflective_key';
export {InjectionToken} from './di/injection_token';

View File

@ -0,0 +1,140 @@
/**
* @license
* Copyright Google Inc. 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 {Type} from '../type';
import {ClassProvider, ClassSansProvider, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, StaticClassProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './provider';
/**
* Information about how a type or `InjectionToken` interfaces with the DI system.
*
* At a minimum, this includes a `factory` which defines how to create the given type `T`, possibly
* requesting injection of other types if necessary.
*
* Optionally, a `providedIn` parameter specifies that the given type belongs to a particular
* `InjectorDef`, `NgModule`, or a special scope (e.g. `'root'`). A value of `null` indicates
* that the injectable does not belong to any scope.
*
* This type is typically generated by the Angular compiler, but can be hand-written if needed.
*
* @experimental
*/
export interface InjectableDef<T> {
providedIn: InjectorType<any>|'root'|'any'|null;
factory: () => T;
}
/**
* Information about the providers to be included in an `Injector` as well as how the given type
* which carries the information should be created by the DI system.
*
* An `InjectorDef` can import other types which have `InjectorDefs`, forming a deep nested
* structure of providers with a defined priority (identically to how `NgModule`s also have
* an import/dependency structure).
*
* @experimental
*/
export interface InjectorDef<T> {
factory: () => T;
// TODO(alxhub): Narrow down the type here once decorators properly change the return type of the
// class they are decorating (to add the ngInjectableDef property for example).
providers: (Type<any>|ValueProvider|ExistingProvider|FactoryProvider|ConstructorProvider|
StaticClassProvider|ClassProvider|any[])[];
imports: (InjectorType<any>|InjectorTypeWithProviders<any>)[];
}
/**
* A `Type` which has an `InjectableDef` static field.
*
* `InjectableDefType`s contain their own Dependency Injection metadata and are usable in an
* `InjectorDef`-based `StaticInjector.
*
* @experimental
*/
export interface InjectableType<T> extends Type<T> { ngInjectableDef: InjectableDef<T>; }
/**
* A type which has an `InjectorDef` static field.
*
* `InjectorDefTypes` can be used to configure a `StaticInjector`.
*
* @experimental
*/
export interface InjectorType<T> extends Type<T> { ngInjectorDef: InjectorDef<T>; }
/**
* Describes the `InjectorDef` equivalent of a `ModuleWithProviders`, an `InjectorDefType` with an
* associated array of providers.
*
* Objects of this type can be listed in the imports section of an `InjectorDef`.
*
* @experimental
*/
export interface InjectorTypeWithProviders<T> {
ngModule: InjectorType<T>;
providers?: (Type<any>|ValueProvider|ExistingProvider|FactoryProvider|ConstructorProvider|
StaticClassProvider|ClassProvider|any[])[];
}
/**
* Construct an `InjectableDef` which defines how a token will be constructed by the DI system, and
* in which injectors (if any) it will be available.
*
* This should be assigned to a static `ngInjectableDef` field on a type, which will then be an
* `InjectableType`.
*
* Options:
* * `providedIn` determines which injectors will include the injectable, by either associating it
* with an `@NgModule` or other `InjectorType`, or by specifying that this injectable should be
* provided in the `'root'` injector, which will be the application-level injector in most apps.
* * `factory` gives the zero argument function which will create an instance of the injectable.
* The factory can call `inject` to access the `Injector` and request injection of dependencies.
*
* @experimental
*/
export function defineInjectable<T>(opts: {
providedIn?: Type<any>| 'root' | null,
factory: () => T,
}): InjectableDef<T> {
return {
providedIn: (opts.providedIn as InjectorType<any>| 'root' | null | undefined) || null,
factory: opts.factory,
};
}
/**
* Construct an `InjectorDef` which configures an injector.
*
* This should be assigned to a static `ngInjectorDef` field on a type, which will then be an
* `InjectorType`.
*
* Options:
*
* * `factory`: an `InjectorType` is an instantiable type, so a zero argument `factory` function to
* create the type must be provided. If that factory function needs to inject arguments, it can
* use the `inject` function.
* * `providers`: an optional array of providers to add to the injector. Each provider must
* either have a factory or point to a type which has an `ngInjectableDef` static property (the
* type must be an `InjectableType`).
* * `imports`: an optional array of imports of other `InjectorType`s or `InjectorTypeWithModule`s
* whose providers will also be added to the injector. Locally provided types will override
* providers from imports.
*
* @experimental
*/
export function defineInjector(options: {factory: () => any, providers?: any[], imports?: any[]}):
InjectorDef<any> {
return {
factory: options.factory,
providers: options.providers || [],
imports: options.imports || [],
};
}

View File

@ -11,6 +11,7 @@ import {Type} from '../type';
import {makeDecorator, makeParamDecorator} from '../util/decorators';
import {getClosureSafeProperty} from '../util/property';
import {InjectableDef, InjectableType, defineInjectable} from './defs';
import {inject, injectArgs} from './injector';
import {ClassSansProvider, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, StaticClassProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './provider';
@ -108,22 +109,6 @@ export function convertInjectableProviderToFactory(
}
}
/**
* Construct an `InjectableDef` which defines how a token will be constructed by the DI system, and
* in which injectors (if any) it will be available.
*
* @experimental
*/
export function defineInjectable<T>(opts: {
providedIn?: Type<any>| 'root' | null,
factory: () => T,
}): InjectableDef<T> {
return {
providedIn: opts.providedIn || null,
factory: opts.factory,
};
}
/**
* Injectable decorator and metadata.
*
@ -132,21 +117,16 @@ export function defineInjectable<T>(opts: {
*/
export const Injectable: InjectableDecorator = makeDecorator(
'Injectable', undefined, undefined, undefined,
(injectableType: Type<any>,
(injectableType: InjectableType<any>,
options: {providedIn?: Type<any>| 'root' | null} & InjectableProvider) => {
if (options && options.providedIn) {
(injectableType as InjectableType<any>).ngInjectableDef = defineInjectable({
if (options && options.providedIn !== undefined) {
injectableType.ngInjectableDef = defineInjectable({
providedIn: options.providedIn,
factory: convertInjectableProviderToFactory(injectableType, options)
});
}
});
export interface InjectableDef<T> {
providedIn: Type<any>|'root'|null;
factory: () => T;
}
/**
* Type representing injectable service.
*

View File

@ -8,7 +8,7 @@
import {Type} from '../type';
import {InjectableDef, defineInjectable} from './injectable';
import {InjectableDef, defineInjectable} from './defs';
/**
* Creates a token that can be used in a DI Provider.
@ -26,8 +26,24 @@ import {InjectableDef, defineInjectable} from './injectable';
* // myInterface is inferred to be MyInterface.
* ```
*
* When creating an `InjectionToken`, you can optionally specify a factory function which returns
* (possibly by creating) a default value of the parameterized type `T`. This sets up the
* `InjectionToken` using this factory as a provider as if it was defined explicitly in the
* application's root injector. If the factory function, which takes zero arguments, needs to inject
* dependencies, it can do so using the `inject` function. See below for an example.
*
* Additionally, if a `factory` is specified you can also specify the `providedIn` option, which
* overrides the above behavior and marks the token as belonging to a particular `@NgModule`. As
* mentioned above, `'root'` is the default value for `providedIn`.
*
* ### Example
*
* #### Tree-shakeable InjectionToken
*
* {@example core/di/ts/injector_spec.ts region='ShakeableInjectionToken'}
*
* #### Plain InjectionToken
*
* {@example core/di/ts/injector_spec.ts region='InjectionToken'}
*
* @stable
@ -54,3 +70,7 @@ export class InjectionToken<T> {
toString(): string { return `InjectionToken ${this._desc}`; }
}
export interface InjectableDefToken<T> extends InjectionToken<T> {
ngInjectableDef: InjectableDef<T>;
}

View File

@ -8,6 +8,8 @@
import {Type} from '../type';
import {stringify} from '../util';
import {InjectableDef, defineInjectable} from './defs';
import {resolveForwardRef} from './forward_ref';
import {InjectionToken} from './injection_token';
import {Inject, Optional, Self, SkipSelf} from './metadata';
@ -17,7 +19,17 @@ export const SOURCE = '__source';
const _THROW_IF_NOT_FOUND = new Object();
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
class _NullInjector implements Injector {
/**
* An InjectionToken that gets the current `Injector` for `createInjector()`-style injectors.
*
* Requesting this token instead of `Injector` allows `StaticInjector` to be tree-shaken from a
* project.
*
* @experimental
*/
export const INJECTOR = new InjectionToken<Injector>('INJECTOR');
export class NullInjector implements Injector {
get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
if (notFoundValue === _THROW_IF_NOT_FOUND) {
throw new Error(`NullInjectorError: No provider for ${stringify(token)}!`);
@ -48,7 +60,7 @@ class _NullInjector implements Injector {
*/
export abstract class Injector {
static THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
static NULL: Injector = new _NullInjector();
static NULL: Injector = new NullInjector();
/**
* Retrieves an instance from the injector based on the provided token.
@ -87,6 +99,11 @@ export abstract class Injector {
return new StaticInjector(options.providers, options.parent, options.name || null);
}
}
static ngInjectableDef = defineInjectable({
providedIn: 'any' as any,
factory: () => inject(INJECTOR),
});
}
@ -100,7 +117,7 @@ const MULTI_PROVIDER_FN = function(): any[] {
return Array.prototype.slice.call(arguments);
};
const GET_PROPERTY_NAME = {} as any;
const USE_VALUE =
export const USE_VALUE =
getClosureSafeProperty<ValueProvider>({provide: String, useValue: GET_PROPERTY_NAME});
const NG_TOKEN_PATH = 'ngTokenPath';
const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
@ -127,6 +144,8 @@ export class StaticInjector implements Injector {
const records = this._records = new Map<any, Record>();
records.set(
Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
records.set(
INJECTOR, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
recursivelyProcessProviders(records, providers);
}

View File

@ -0,0 +1,408 @@
/**
* @license
* Copyright Google Inc. 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 {OnDestroy} from '../metadata/lifecycle_hooks';
import {Type} from '../type';
import {stringify} from '../util';
import {InjectableDef, InjectableType, InjectorDef, InjectorType, InjectorTypeWithProviders} from './defs';
import {resolveForwardRef} from './forward_ref';
import {InjectableDefToken, InjectionToken} from './injection_token';
import {INJECTOR, InjectFlags, Injector, NullInjector, THROW_IF_NOT_FOUND, USE_VALUE, inject, injectArgs, setCurrentInjector} from './injector';
import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, Provider, StaticClassProvider, TypeProvider, ValueProvider} from './provider';
import {APP_ROOT} from './scope';
/**
* Internal type for a single provider in a deep provider array.
*/
type SingleProvider = TypeProvider | ValueProvider | ClassProvider | ConstructorProvider |
ExistingProvider | FactoryProvider | StaticClassProvider;
/**
* Marker which indicates that a value has not yet been created from the factory function.
*/
const NOT_YET = {};
/**
* Marker which indicates that the factory function for a token is in the process of being called.
*
* If the injector is asked to inject a token with its value set to CIRCULAR, that indicates
* injection of a dependency has recursively attempted to inject the original token, and there is
* a circular dependency among the providers.
*/
const CIRCULAR = {};
const EMPTY_ARRAY = [] as any[];
/**
* A lazily initialized NullInjector.
*/
let NULL_INJECTOR: Injector|undefined = undefined;
function getNullInjector(): Injector {
if (NULL_INJECTOR === undefined) {
NULL_INJECTOR = new NullInjector();
}
return NULL_INJECTOR;
}
/**
* An entry in the injector which tracks information about the given token, including a possible
* current value.
*/
interface Record<T> {
factory: (() => T)|undefined;
value: T|{};
multi: any[]|undefined;
}
/**
* Create a new `Injector` which is configured using `InjectorDefType`s.
*
* @experimental
*/
export function createInjector(
defType: /* InjectorDefType<any> */ any, parent: Injector | null = null): Injector {
parent = parent || getNullInjector();
return new R3Injector(defType, parent);
}
export class R3Injector {
/**
* Map of tokens to records which contain the instances of those tokens.
*/
private records = new Map<Type<any>|InjectionToken<any>, Record<any>>();
/**
* The transitive set of `InjectorDefType`s which define this injector.
*/
private injectorDefTypes = new Set<InjectorType<any>>();
/**
* Set of values instantiated by this injector which contain `ngOnDestroy` lifecycle hooks.
*/
private onDestroy = new Set<OnDestroy>();
/**
* Flag indicating this injector provides the APP_ROOT_SCOPE token, and thus counts as the
* root scope.
*/
private readonly isRootInjector: boolean;
/**
* Flag indicating that this injector was previously destroyed.
*/
private destroyed = false;
constructor(def: InjectorType<any>, readonly parent: Injector) {
// Start off by creating Records for every provider declared in every InjectorDefType
// included transitively in `def`.
deepForEach(
[def], injectorDef => this.processInjectorType(injectorDef, new Set<InjectorType<any>>()));
// Make sure the INJECTOR token provides this injector.
this.records.set(INJECTOR, makeRecord(undefined, this));
// Detect whether this injector has the APP_ROOT_SCOPE token and thus should provide
// any injectable scoped to APP_ROOT_SCOPE.
this.isRootInjector = this.records.has(APP_ROOT);
// Eagerly instantiate the InjectorDefType classes themselves.
this.injectorDefTypes.forEach(defType => this.get(defType));
}
/**
* Destroy the injector and release references to every instance or provider associated with it.
*
* Also calls the `OnDestroy` lifecycle hooks of every instance that was created for which a
* hook was found.
*/
destroy(): void {
this.assertNotDestroyed();
// Set destroyed = true first, in case lifecycle hooks re-enter destroy().
this.destroyed = true;
try {
// Call all the lifecycle hooks.
this.onDestroy.forEach(service => service.ngOnDestroy());
} finally {
// Release all references.
this.records.clear();
this.onDestroy.clear();
this.injectorDefTypes.clear();
}
}
get<T>(
token: Type<T>|InjectionToken<T>, notFoundValue: any = THROW_IF_NOT_FOUND,
flags = InjectFlags.Default): T {
this.assertNotDestroyed();
// Set the injection context.
const previousInjector = setCurrentInjector(this);
try {
// Check for the SkipSelf flag.
if (!(flags & InjectFlags.SkipSelf)) {
// SkipSelf isn't set, check if the record belongs to this injector.
let record: Record<T>|undefined = this.records.get(token);
if (record === undefined) {
// No record, but maybe the token is scoped to this injector. Look for an ngInjectableDef
// with a scope matching this injector.
const def = couldBeInjectableType(token) &&
(token as InjectableType<any>| InjectableDefToken<any>).ngInjectableDef ||
undefined;
if (def !== undefined && this.injectableDefInScope(def)) {
// Found an ngInjectableDef and it's scoped to this injector. Pretend as if it was here
// all along.
record = injectableDefRecord(token);
this.records.set(token, record);
}
}
// If a record was found, get the instance for it and return it.
if (record !== undefined) {
return this.hydrate(token, record);
}
}
// Select the next injector based on the Self flag - if self is set, the next injector is
// the NullInjector, otherwise it's the parent.
let next = !(flags & InjectFlags.Self) ? this.parent : getNullInjector();
return this.parent.get(token, notFoundValue);
} finally {
// Lastly, clean up the state by restoring the previous injector.
setCurrentInjector(previousInjector);
}
}
private assertNotDestroyed(): void {
if (this.destroyed) {
throw new Error('Injector has already been destroyed.');
}
}
/**
* Add an `InjectorDefType` or `InjectorDefTypeWithProviders` and all of its transitive providers
* to this injector.
*/
private processInjectorType(
defOrWrappedDef: InjectorType<any>|InjectorTypeWithProviders<any>,
parents: Set<InjectorType<any>>) {
defOrWrappedDef = resolveForwardRef(defOrWrappedDef);
// Either the defOrWrappedDef is an InjectorDefType (with ngInjectorDef) or an
// InjectorDefTypeWithProviders (aka ModuleWithProviders). Detecting either is a megamorphic
// read, so care is taken to only do the read once.
// First attempt to read the ngInjectorDef.
let def = (defOrWrappedDef as InjectorType<any>).ngInjectorDef as(InjectorDef<any>| undefined);
// If that's not present, then attempt to read ngModule from the InjectorDefTypeWithProviders.
const ngModule =
(def == null) && (defOrWrappedDef as InjectorTypeWithProviders<any>).ngModule || undefined;
// Determine the InjectorDefType. In the case where `defOrWrappedDef` is an `InjectorDefType`,
// then this is easy. In the case of an InjectorDefTypeWithProviders, then the definition type
// is the `ngModule`.
const defType: InjectorType<any> =
(ngModule === undefined) ? (defOrWrappedDef as InjectorType<any>) : ngModule;
// If defOrWrappedType was an InjectorDefTypeWithProviders, then .providers may hold some
// extra providers.
const providers =
(ngModule !== undefined) && (defOrWrappedDef as InjectorTypeWithProviders<any>).providers ||
EMPTY_ARRAY;
// Finally, if defOrWrappedType was an `InjectorDefTypeWithProviders`, then the actual
// `InjectorDef` is on its `ngModule`.
if (ngModule !== undefined) {
def = ngModule.ngInjectorDef;
}
// If no definition was found, throw.
if (def == null) {
throw new Error(`Type ${stringify(defType)} is missing an ngInjectorDef definition.`);
}
// Check for circular dependencies.
if (parents.has(defType)) {
throw new Error(`Circular dependency: type ${stringify(defType)} ends up importing itself.`);
}
// Track the InjectorDefType and add a provider for it.
this.injectorDefTypes.add(defType);
this.records.set(defType, makeRecord(def.factory));
// Add providers in the same way that @NgModule resolution did:
// First, include providers from any imports.
if (def.imports != null) {
// Before processing defType's imports, add it to the set of parents. This way, if it ends
// up deeply importing itself, this can be detected.
parents.add(defType);
try {
deepForEach(def.imports, imported => this.processInjectorType(imported, parents));
} finally {
// Remove it from the parents set when finished.
parents.delete(defType);
}
}
// Next, include providers listed on the definition itself.
if (def.providers != null) {
deepForEach(def.providers, provider => this.processProvider(provider));
}
// Finally, include providers from an InjectorDefTypeWithProviders if there was one.
deepForEach(providers, provider => this.processProvider(provider));
}
/**
* Process a `SingleProvider` and add it.
*/
private processProvider(provider: SingleProvider): void {
// Determine the token from the provider. Either it's its own token, or has a {provide: ...}
// property.
provider = resolveForwardRef(provider);
let token: any = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide);
// Construct a `Record` for the provider.
const record = providerToRecord(provider);
if (!isTypeProvider(provider) && provider.multi === true) {
// If the provider indicates that it's a multi-provider, process it specially.
// First check whether it's been defined already.
let multiRecord = this.records.get(token);
if (multiRecord) {
// It has. Throw a nice error if
if (multiRecord.multi === undefined) {
throw new Error(`Mixed multi-provider for ${token}.`);
}
} else {
token = provider;
multiRecord = makeRecord(undefined, NOT_YET, true);
multiRecord.factory = () => injectArgs(multiRecord !.multi !);
this.records.set(token, multiRecord);
}
token = provider;
multiRecord.multi !.push(provider);
}
const existing = this.records.get(token);
if (existing && existing.multi !== undefined) {
throw new Error(`Mixed multi-provider for ${token}`);
}
this.records.set(token, record);
}
private hydrate<T>(token: Type<T>|InjectionToken<T>, record: Record<T>): T {
if (record.value === CIRCULAR) {
throw new Error(`Circular dep for ${stringify(token)}`);
} else if (record.value === NOT_YET) {
record.value = CIRCULAR;
record.value = record.factory !();
}
if (typeof record.value === 'object' && record.value && hasOnDestroy(record.value)) {
this.onDestroy.add(record.value);
}
return record.value as T;
}
private injectableDefInScope(def: InjectableDef<any>): boolean {
if (!def.providedIn) {
return false;
} else if (typeof def.providedIn === 'string') {
return def.providedIn === 'any' || (def.providedIn === 'root' && this.isRootInjector);
} else {
return this.injectorDefTypes.has(def.providedIn);
}
}
}
function injectableDefRecord(token: Type<any>| InjectionToken<any>): Record<any> {
const def = (token as InjectableType<any>).ngInjectableDef;
if (def === undefined) {
throw new Error(`Type ${stringify(token)} is missing an ngInjectableDef definition.`);
}
return makeRecord(def.factory);
}
function providerToRecord(provider: SingleProvider): Record<any> {
let token = resolveForwardRef(provider);
let value: any = NOT_YET;
let factory: (() => any)|undefined = undefined;
if (isTypeProvider(provider)) {
return injectableDefRecord(provider);
} else {
token = resolveForwardRef(provider.provide);
if (isValueProvider(provider)) {
value = provider.useValue;
} else if (isExistingProvider(provider)) {
factory = () => inject(provider.useExisting);
} else if (isFactoryProvider(provider)) {
factory = () => provider.useFactory(...injectArgs(provider.deps || []));
} else {
const classRef = (provider as StaticClassProvider | ClassProvider).useClass || token;
if (hasDeps(provider)) {
factory = () => new (classRef)(...injectArgs(provider.deps));
} else {
return injectableDefRecord(classRef);
}
}
}
return makeRecord(factory, value);
}
function makeRecord<T>(
factory: (() => T) | undefined, value: T | {} = NOT_YET, multi: boolean = false): Record<T> {
return {
factory: factory,
value: value,
multi: multi ? [] : undefined,
};
}
function deepForEach<T>(input: (T | any[])[], fn: (value: T) => void): void {
input.forEach(value => Array.isArray(value) ? deepForEach(value, fn) : fn(value));
}
function isValueProvider(value: SingleProvider): value is ValueProvider {
return USE_VALUE in value;
}
function isExistingProvider(value: SingleProvider): value is ExistingProvider {
return !!(value as ExistingProvider).useExisting;
}
function isFactoryProvider(value: SingleProvider): value is FactoryProvider {
return !!(value as FactoryProvider).useFactory;
}
function isClassProvider(value: SingleProvider): value is ClassProvider {
return !!(value as ClassProvider).useClass;
}
function isTypeProvider(value: SingleProvider): value is TypeProvider {
return typeof value === 'function';
}
function hasDeps(value: ClassProvider | ConstructorProvider | StaticClassProvider):
value is ClassProvider&{deps: any[]} {
return !!(value as any).deps;
}
function hasOnDestroy(value: any): value is OnDestroy {
return typeof value === 'object' && value != null && (value as OnDestroy).ngOnDestroy &&
typeof(value as OnDestroy).ngOnDestroy === 'function';
}
function couldBeInjectableType(value: any): value is Type<any>|InjectionToken<any> {
return (typeof value === 'function') ||
(typeof value === 'object' && value instanceof InjectionToken);
}

View File

@ -6,10 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Provider} from '../di';
import {InjectorDef, InjectorType, defineInjector} from '../di/defs';
import {convertInjectableProviderToFactory} from '../di/injectable';
import {Provider} from '../di/provider';
import {Type} from '../type';
import {TypeDecorator, makeDecorator} from '../util/decorators';
/**
* A wrapper around a module that also includes the providers.
*
@ -190,5 +193,17 @@ export interface NgModule {
* @stable
* @Annotation
*/
export const NgModule: NgModuleDecorator =
makeDecorator('NgModule', (ngModule: NgModule) => ngModule);
export const NgModule: NgModuleDecorator = makeDecorator(
'NgModule', (ngModule: NgModule) => ngModule, undefined, undefined,
(moduleType: InjectorType<any>, metadata: NgModule) => {
let imports = (metadata && metadata.imports) || [];
if (metadata && metadata.exports) {
imports = [...imports, metadata.exports];
}
moduleType.ngInjectorDef = defineInjector({
factory: convertInjectableProviderToFactory(moduleType, {useClass: moduleType}),
providers: metadata && metadata.providers,
imports: imports,
});
});

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {InjectableDef} from '../di/defs';
import {resolveForwardRef} from '../di/forward_ref';
import {InjectableDef} from '../di/injectable';
import {InjectFlags, Injector, setCurrentInjector} from '../di/injector';
import {INJECTOR, InjectFlags, Injector, setCurrentInjector} from '../di/injector';
import {APP_ROOT} from '../di/scope';
import {NgModuleRef} from '../linker/ng_module_factory';
import {stringify} from '../util';

View File

@ -8,7 +8,8 @@
import {isDevMode} from '../application_ref';
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
import {InjectableType, Injector} from '../di';
import {Injector} from '../di';
import {InjectableType} from '../di/injectable';
import {ErrorHandler} from '../error_handler';
import {ComponentFactory} from '../linker/component_factory';
import {NgModuleRef} from '../linker/ng_module_factory';

View File

@ -0,0 +1,54 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
load("//tools/symbol-extractor:index.bzl", "js_expected_symbol_test")
load("//packages/bazel/src:ng_rollup_bundle.bzl", "ng_rollup_bundle")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
ts_library(
name = "injection",
srcs = [
"index.ts",
"usage.ts",
],
deps = [
"//packages/core",
],
)
ng_rollup_bundle(
name = "bundle",
# TODO(alexeagle): This is inconsistent.
# We try to teach users to always have their workspace at the start of a
# path, to disambiguate from other workspaces.
# Here, the rule implementation is looking in an execroot where the layout
# has an "external" directory for external dependencies.
# This should probably start with "angular/" and let the rule deal with it.
entry_point = "packages/core/test/bundling/injection/index.js",
deps = [
":injection",
"//packages/core",
],
)
ts_library(
name = "test_lib",
testonly = 1,
srcs = glob(["*_spec.ts"]),
deps = [
":injection",
"//packages:types",
"//packages/core/testing",
],
)
jasmine_node_test(
name = "test",
deps = [":test_lib"],
)
js_expected_symbol_test(
name = "symbol_test",
src = ":bundle.min_debug.js",
golden = ":bundle.golden_symbols.json",
)

View File

@ -0,0 +1,161 @@
[
{
"name": "APP_ROOT"
},
{
"name": "CIRCULAR$1"
},
{
"name": "EMPTY_ARRAY$1"
},
{
"name": "GET_PROPERTY_NAME$1"
},
{
"name": "INJECTOR$1"
},
{
"name": "Inject"
},
{
"name": "InjectionToken"
},
{
"name": "NOT_YET"
},
{
"name": "NULL_INJECTOR$1"
},
{
"name": "NullInjector"
},
{
"name": "Optional"
},
{
"name": "PARAMETERS"
},
{
"name": "R3Injector"
},
{
"name": "ScopedService"
},
{
"name": "Self"
},
{
"name": "SkipSelf"
},
{
"name": "Symbol$1"
},
{
"name": "THROW_IF_NOT_FOUND"
},
{
"name": "USE_VALUE$1"
},
{
"name": "_THROW_IF_NOT_FOUND"
},
{
"name": "__global$1"
},
{
"name": "__read"
},
{
"name": "__self$1"
},
{
"name": "__spread"
},
{
"name": "__window$1"
},
{
"name": "_currentInjector"
},
{
"name": "_root"
},
{
"name": "couldBeInjectableType"
},
{
"name": "createInjector"
},
{
"name": "deepForEach"
},
{
"name": "defineInjectable"
},
{
"name": "defineInjector"
},
{
"name": "forwardRef"
},
{
"name": "getClosureSafeProperty$1"
},
{
"name": "getNullInjector"
},
{
"name": "getSymbolObservable"
},
{
"name": "hasDeps"
},
{
"name": "hasOnDestroy"
},
{
"name": "inject"
},
{
"name": "injectArgs"
},
{
"name": "injectableDefRecord"
},
{
"name": "isExistingProvider"
},
{
"name": "isFactoryProvider"
},
{
"name": "isTypeProvider"
},
{
"name": "isValueProvider"
},
{
"name": "makeMetadataCtor"
},
{
"name": "makeParamDecorator"
},
{
"name": "makeRecord"
},
{
"name": "providerToRecord"
},
{
"name": "resolveForwardRef"
},
{
"name": "setCurrentInjector"
},
{
"name": "stringify"
},
{
"name": "symbolIteratorPonyfill"
}
]

View File

@ -0,0 +1,11 @@
/**
* @license
* Copyright Google Inc. 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 {INJECTOR, ScopedService} from './usage';
INJECTOR.get(ScopedService).doSomething();

View File

@ -0,0 +1,22 @@
/**
* @license
* Copyright Google Inc. 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 * as fs from 'fs';
import * as path from 'path';
import {INJECTOR, ScopedService} from './usage';
const UTF8 = {
encoding: 'utf-8'
};
const PACKAGE = 'angular/packages/core/test/bundling/hello_world';
describe('functional test for injection system bundling', () => {
it('should be able to inject the scoped service',
() => { expect(INJECTOR.get(ScopedService) instanceof ScopedService).toBe(true); });
});

View File

@ -0,0 +1,37 @@
/**
* @license
* Copyright Google Inc. 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 {InjectableDef, Injector, InjectorDef, createInjector, defineInjectable, defineInjector} from '@angular/core';
export class RootService {
static ngInjectableDef = defineInjectable({
providedIn: 'root',
factory: () => new RootService(),
});
}
export class ScopedService {
static ngInjectableDef = defineInjectable({
providedIn: null,
factory: () => new ScopedService(),
});
doSomething(): void {
// tslint:disable-next-line:no-console
console.log('Ensure this isn\'t tree-shaken.');
}
}
export class DefinedInjector {
static ngInjectorDef = defineInjector({
factory: () => new DefinedInjector(),
providers: [ScopedService],
});
}
export const INJECTOR = createInjector(DefinedInjector);

View File

@ -0,0 +1,224 @@
/**
* @license
* Copyright Google Inc. 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 {defineInjectable, defineInjector} from '../../src/di/defs';
import {InjectionToken} from '../../src/di/injection_token';
import {INJECTOR, Injector, inject} from '../../src/di/injector';
import {R3Injector, createInjector} from '../../src/di/r3_injector';
describe('InjectorDef-based createInjector()', () => {
class CircularA {
static ngInjectableDef = defineInjectable({
providedIn: null,
factory: () => inject(CircularB),
});
}
class CircularB {
static ngInjectableDef = defineInjectable({
providedIn: null,
factory: () => inject(CircularA),
});
}
class Service {
static ngInjectableDef = defineInjectable({
providedIn: null,
factory: () => new Service(),
});
}
class StaticService {
constructor(readonly dep: Service) {}
}
const SERVICE_TOKEN = new InjectionToken<Service>('SERVICE_TOKEN');
const STATIC_TOKEN = new InjectionToken<StaticService>('STATIC_TOKEN');
class ServiceWithDep {
constructor(readonly service: Service) {}
static ngInjectableDef = defineInjectable({
providedIn: null,
factory: () => new ServiceWithDep(inject(Service)),
});
}
class ServiceTwo {
static ngInjectableDef = defineInjectable({
providedIn: null,
factory: () => new ServiceTwo(),
});
}
let deepServiceDestroyed = false;
class DeepService {
static ngInjectableDef = defineInjectable({
providedIn: null,
factory: () => new DeepService(),
});
ngOnDestroy(): void { deepServiceDestroyed = true; }
}
let eagerServiceCreated: boolean = false;
class EagerService {
static ngInjectableDef = defineInjectable({
providedIn: undefined,
factory: () => new EagerService(),
});
constructor() { eagerServiceCreated = true; }
}
let deepModuleCreated: boolean = false;
class DeepModule {
constructor(eagerService: EagerService) { deepModuleCreated = true; }
static ngInjectorDef = defineInjector({
factory: () => new DeepModule(inject(EagerService)),
imports: undefined,
providers: [
EagerService,
{provide: DeepService, useFactory: () => { throw new Error('Not overridden!'); }},
],
});
static safe() {
return {
ngModule: DeepModule,
providers: [{provide: DeepService}],
};
}
}
class IntermediateModule {
static ngInjectorDef = defineInjector({
factory: () => new IntermediateModule(),
imports: [DeepModule.safe()],
providers: [],
});
}
class Module {
static ngInjectorDef = defineInjector({
factory: () => new Module(),
imports: [IntermediateModule],
providers: [
ServiceWithDep,
Service,
{provide: SERVICE_TOKEN, useExisting: Service},
CircularA,
CircularB,
{provide: STATIC_TOKEN, useClass: StaticService, deps: [Service]},
],
});
}
class OtherModule {
static ngInjectorDef = defineInjector({
factory: () => new OtherModule(),
imports: undefined,
providers: [],
});
}
class ScopedService {
static ngInjectableDef = defineInjectable({
providedIn: Module,
factory: () => new ScopedService(),
});
}
class WrongScopeService {
static ngInjectableDef = defineInjectable({
providedIn: OtherModule,
factory: () => new WrongScopeService(),
});
}
let injector: Injector;
beforeEach(() => {
deepModuleCreated = eagerServiceCreated = deepServiceDestroyed = false;
injector = createInjector(Module);
});
it('injects a simple class', () => {
const instance = injector.get(Service);
expect(instance instanceof Service).toBeTruthy();
expect(injector.get(Service)).toBe(instance);
});
it('throws an error when a token is not found',
() => { expect(() => injector.get(ServiceTwo)).toThrow(); });
it('returns the default value if a provider isn\'t present',
() => { expect(injector.get(ServiceTwo, null)).toBeNull(); });
it('injects a service with dependencies', () => {
const instance = injector.get(ServiceWithDep);
expect(instance instanceof ServiceWithDep);
expect(instance.service).toBe(injector.get(Service));
});
it('injects a token with useExisting', () => {
const instance = injector.get(SERVICE_TOKEN);
expect(instance).toBe(injector.get(Service));
});
it('instantiates a class with useClass and deps', () => {
const instance = injector.get(STATIC_TOKEN);
expect(instance instanceof StaticService).toBeTruthy();
expect(instance.dep).toBe(injector.get(Service));
});
it('throws an error on circular deps',
() => { expect(() => injector.get(CircularA)).toThrow(); });
it('allows injecting itself via INJECTOR',
() => { expect(injector.get(INJECTOR)).toBe(injector); });
it('allows injecting itself via Injector',
() => { expect(injector.get(Injector)).toBe(injector); });
it('allows injecting a deeply imported service',
() => { expect(injector.get(DeepService) instanceof DeepService).toBeTruthy(); });
it('allows injecting a scoped service', () => {
const instance = injector.get(ScopedService);
expect(instance instanceof ScopedService).toBeTruthy();
expect(instance).toBe(injector.get(ScopedService));
});
it('does not create instances of a service not in scope',
() => { expect(injector.get(WrongScopeService, null)).toBeNull(); });
it('eagerly instantiates the injectordef types', () => {
expect(deepModuleCreated).toBe(true, 'DeepModule not instantiated');
expect(eagerServiceCreated).toBe(true, 'EagerSerivce not instantiated');
});
it('calls ngOnDestroy on services when destroyed', () => {
injector.get(DeepService);
(injector as R3Injector).destroy();
expect(deepServiceDestroyed).toBe(true, 'DeepService not destroyed');
});
it('does not allow injection after destroy', () => {
(injector as R3Injector).destroy();
expect(() => injector.get(DeepService)).toThrowError('Injector has already been destroyed.');
});
it('does not allow double destroy', () => {
(injector as R3Injector).destroy();
expect(() => (injector as R3Injector).destroy())
.toThrowError('Injector has already been destroyed.');
});
});

View File

@ -473,7 +473,7 @@ function factoryFn(a: any){}
describe('displayName', () => {
it('should work', () => {
expect(Injector.create([Engine.PROVIDER, {provide: BrokenEngine, useValue: null}]).toString())
.toEqual('StaticInjector[Injector, Engine, BrokenEngine]');
.toEqual('StaticInjector[Injector, InjectionToken INJECTOR, Engine, BrokenEngine]');
});
});
}

View File

@ -6,11 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, ContentChild, Directive, Injectable, Injector, Input, NgModule, NgModuleFactory, NgModuleRef, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '../../../src/core';
import {Component, ContentChild, Directive, Injectable, Injector, InjectorDef, Input, NgModule, NgModuleFactory, NgModuleRef, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, defineInjector} from '../../../src/core';
import * as r3 from '../../../src/render3/index';
import {$pending_pr_22458$} from './pending_api_spec';
const details_elided = {
type: Object,
} as any;
@ -60,7 +58,7 @@ export class LibBComponent {
@NgModule({declarations: [LibAComponent], imports: []})
export class LibBModule {
// COMPILER GENERATED
static ngInjectorDef = $pending_pr_22458$.defineInjector(details_elided);
static ngInjectorDef = defineInjector(details_elided);
}
// END FILE: node_modules/libB/module.ts
// BEGIN FILE: node_modules/libB/module.metadata.json
@ -92,7 +90,7 @@ export class AppComponent {
@NgModule({declarations: [LibAComponent], imports: []})
export class AppModule {
// COMPILER GENERATED
static ngInjectorDef = $pending_pr_22458$.defineInjector(details_elided);
static ngInjectorDef = defineInjector(details_elided);
}
// END FILE: src/app.ts
@ -113,7 +111,7 @@ function ngBackPatch_node_modules_libB_module_LibAComponent() {
}
function ngBackPatch_node_modules_libB_module_LibAModule() {
(LibAModule as any).ngInjectorDef = $pending_pr_22458$.defineInjector(details_elided);
(LibAModule as any).ngInjectorDef = defineInjector(details_elided);
}
export const AppModuleFactory: NgModuleFactory<AppModule>&{patchedDeps: boolean} = {

View File

@ -6,12 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Inject, InjectFlags, Injectable, Injector, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, SkipSelf, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core';
import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, INJECTOR, Inject, InjectFlags, Injectable, InjectableDef, Injector, InjectorDef, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, SkipSelf, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, defineInjectable, defineInjector} from '../../../src/core';
import * as $r3$ from '../../../src/core_render3_private_export';
import {renderComponent, toHtml} from '../render_util';
import {$pending_pr_22458$} from './pending_api_spec';
/// See: `normative.md`
@ -118,7 +116,7 @@ describe('injection', () => {
@Injectable()
class ServiceA {
// NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable({
static ngInjectableDef = defineInjectable({
factory: function ServiceA_Factory() { return new ServiceA(); },
});
// /NORMATIVE
@ -127,7 +125,7 @@ describe('injection', () => {
@Injectable()
class ServiceB {
// NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable({
static ngInjectableDef = defineInjectable({
factory: function ServiceA_Factory() { return new ServiceB(); },
});
// /NORMATIVE
@ -146,8 +144,7 @@ describe('injection', () => {
tag: 'my-app',
factory: function MyApp_Factory() {
return new MyApp(
$r3$.ɵdirectiveInject(ServiceA), $r3$.ɵdirectiveInject(ServiceB),
$pending_pr_22458$.injectInjector());
$r3$.ɵdirectiveInject(ServiceA), $r3$.ɵdirectiveInject(ServiceB), inject(INJECTOR));
},
/** */
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {},
@ -169,10 +166,9 @@ describe('injection', () => {
constructor(@Inject(String) name: String, injector: Injector) {}
// NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable({
static ngInjectableDef = defineInjectable({
factory: function ServiceA_Factory() {
return new ServiceA(
$pending_pr_22458$.inject(String), $pending_pr_22458$.injectInjector());
return new ServiceA(inject(String), inject(INJECTOR));
},
});
// /NORMATIVE
@ -182,11 +178,9 @@ describe('injection', () => {
class ServiceB {
constructor(serviceA: ServiceA, @SkipSelf() injector: Injector) {}
// NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable({
static ngInjectableDef = defineInjectable({
factory: function ServiceA_Factory() {
return new ServiceB(
$pending_pr_22458$.inject(ServiceA),
$pending_pr_22458$.injectInjector(InjectFlags.SkipSelf));
return new ServiceB(inject(ServiceA), inject(INJECTOR, undefined, InjectFlags.SkipSelf));
},
});
// /NORMATIVE

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, ContentChild, Directive, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '../../../src/core';
import {Component, ContentChild, Directive, Injectable, InjectableDef, InjectorDef, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, defineInjectable, defineInjector} from '../../../src/core';
import * as r3 from '../../../src/render3/index';
import {$pending_pr_22458$} from './pending_api_spec';
/**
* GOALS:
@ -29,7 +29,7 @@ class ThirdPartyClass {
@Injectable()
class CompiledWithIvy {
// NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable(
static ngInjectableDef = defineInjectable(
{factory: function CompileWithIvy_Factory() { return new CompiledWithIvy(); }});
// /NORMATIVE
}
@ -38,7 +38,7 @@ class CompiledWithIvy {
@NgModule({providers: [ThirdPartyClass, CompiledWithIvy]})
class CompiledWithIvyModule {
// NORMATIVE
static ngInjectorDef = $pending_pr_22458$.defineInjector({
static ngInjectorDef = defineInjector({
providers: [ThirdPartyClass, CompiledWithIvy],
factory: function CompiledWithIvyModule_Factory() { return new CompiledWithIvyModule(); }
});
@ -72,7 +72,7 @@ function ngPatch_depsOf_CompiledWithIvyModule() {
}
function ngPatch_node_modules_some_library_path_public_CompileWithIvy() {
/** @__BUILD_OPTIMIZER_COLOCATE__ */
(ThirdPartyClass as any).ngInjectableDef = $pending_pr_22458$.defineInjectable(
(ThirdPartyClass as any).ngInjectableDef = defineInjectable(
{factory: function CompileWithIvy_Factory() { return new ThirdPartyClass(); }});
}
// /NORMATIVE

View File

@ -1,28 +0,0 @@
/**
* @license
* Copyright Google Inc. 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 {InjectFlags, InjectionToken, Injector, Type} from '@angular/core';
// TODO: remove once https://github.com/angular/angular/pull/22458 lands
export class $pending_pr_22458$ {
static defineInjectable<T>({providerFor, factory}: {providerFor?: Type<any>, factory: () => T}):
{providerFor: Type<any>| null, factory: () => T} {
return {providerFor: providerFor || null, factory: factory};
}
static defineInjector<T>({factory, providers}: {factory: () => T, providers: any[]}):
{factory: () => T, providers: any[]} {
return {factory: factory, providers: providers};
}
static injectInjector(flags?: InjectFlags): Injector { return null !; }
static inject<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags): T { return null as any; }
}

View File

@ -7,11 +7,10 @@
*/
import {NgForOf, NgForOfContext} from '@angular/common';
import {Component, ContentChild, Directive, EventEmitter, Injectable, Input, NgModule, OnDestroy, Optional, Output, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
import {Component, ContentChild, Directive, EventEmitter, Injectable, InjectableDef, InjectorDef, Input, NgModule, OnDestroy, Optional, Output, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, defineInjectable, defineInjector} from '@angular/core';
import {withBody} from '@angular/core/testing';
import * as r3 from '../../../src/render3/index';
import {$pending_pr_22458$} from './pending_api_spec';
/// See: `normative.md`
@ -33,7 +32,7 @@ class AppState {
];
// NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable({factory: () => new AppState()});
static ngInjectableDef = defineInjectable({factory: () => new AppState()});
// /NORMATIVE
}
@ -158,7 +157,7 @@ const e1_attrs = ['type', 'checkbox'];
})
class ToDoAppModule {
// NORMATIVE
static ngInjectorDef = $pending_pr_22458$.defineInjector({
static ngInjectorDef = defineInjector({
factory: () => new ToDoAppModule(),
providers: [AppState],
});

View File

@ -7,7 +7,7 @@
*/
import {NgModuleRef} from '@angular/core';
import {InjectableDef} from '@angular/core/src/di/injectable';
import {InjectableDef, defineInjectable} from '@angular/core/src/di/defs';
import {InjectFlags, Injector, inject} from '@angular/core/src/di/injector';
import {makePropDecorator} from '@angular/core/src/util/decorators';
import {NgModuleDefinition, NgModuleProviderDef, NodeFlags} from '@angular/core/src/view';
@ -24,68 +24,68 @@ class MyChildModule {}
class NotMyModule {}
class Bar {
static ngInjectableDef: InjectableDef<Bar> = {
static ngInjectableDef: InjectableDef<Bar> = defineInjectable({
factory: () => new Bar(),
providedIn: MyModule,
};
});
}
class Baz {
static ngInjectableDef: InjectableDef<Baz> = {
static ngInjectableDef: InjectableDef<Baz> = defineInjectable({
factory: () => new Baz(),
providedIn: NotMyModule,
};
});
}
class HasNormalDep {
constructor(public foo: Foo) {}
static ngInjectableDef: InjectableDef<HasNormalDep> = {
static ngInjectableDef: InjectableDef<HasNormalDep> = defineInjectable({
factory: () => new HasNormalDep(inject(Foo)),
providedIn: MyModule,
};
});
}
class HasDefinedDep {
constructor(public bar: Bar) {}
static ngInjectableDef: InjectableDef<HasDefinedDep> = {
static ngInjectableDef: InjectableDef<HasDefinedDep> = defineInjectable({
factory: () => new HasDefinedDep(inject(Bar)),
providedIn: MyModule,
};
});
}
class HasOptionalDep {
constructor(public baz: Baz|null) {}
static ngInjectableDef: InjectableDef<HasOptionalDep> = {
static ngInjectableDef: InjectableDef<HasOptionalDep> = defineInjectable({
factory: () => new HasOptionalDep(inject(Baz, null)),
providedIn: MyModule,
};
});
}
class ChildDep {
static ngInjectableDef: InjectableDef<ChildDep> = {
static ngInjectableDef: InjectableDef<ChildDep> = defineInjectable({
factory: () => new ChildDep(),
providedIn: MyChildModule,
};
});
}
class FromChildWithOptionalDep {
constructor(public baz: Baz|null) {}
static ngInjectableDef: InjectableDef<FromChildWithOptionalDep> = {
static ngInjectableDef: InjectableDef<FromChildWithOptionalDep> = defineInjectable({
factory: () => new FromChildWithOptionalDep(inject(Baz, null, InjectFlags.Default)),
providedIn: MyChildModule,
};
});
}
class FromChildWithSkipSelfDep {
constructor(public depFromParent: ChildDep|null, public depFromChild: Bar|null) {}
static ngInjectableDef: InjectableDef<FromChildWithSkipSelfDep> = {
static ngInjectableDef: InjectableDef<FromChildWithSkipSelfDep> = defineInjectable({
factory: () => new FromChildWithSkipSelfDep(
inject(ChildDep, null, InjectFlags.SkipSelf), inject(Bar, null, InjectFlags.Self)),
providedIn: MyChildModule,
};
});
}
function makeProviders(classes: any[], modules: any[]): NgModuleDefinition {

View File

@ -37,6 +37,9 @@ export class JitReflector implements CompileReflector {
annotations(typeOrFunc: /*Type*/ any): any[] {
return this.reflectionCapabilities.annotations(typeOrFunc);
}
shallowAnnotations(typeOrFunc: /*Type*/ any): any[] {
throw new Error('Not supported in JIT mode');
}
propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]} {
return this.reflectionCapabilities.propMetadata(typeOrFunc);
}

View File

@ -2,6 +2,7 @@
load("@build_bazel_rules_nodejs//:defs.bzl", _npm_package = "npm_package")
load("@build_bazel_rules_typescript//:defs.bzl", _ts_library = "ts_library", _ts_web_test = "ts_web_test")
load("//packages/bazel:index.bzl", _ng_module = "ng_module", _ng_package = "ng_package")
load("//packages/bazel/src:ng_module.bzl", _ivy_ng_module = "internal_ivy_ng_module")
DEFAULT_TSCONFIG = "//packages:tsconfig-build.json"
@ -78,4 +79,9 @@ def ts_web_test(bootstrap = [], deps = [], **kwargs):
_ts_web_test(
bootstrap = bootstrap,
deps = local_deps,
**kwargs)
**kwargs)
def ivy_ng_module(name, tsconfig = None, **kwargs):
if not tsconfig:
tsconfig = DEFAULT_TSCONFIG
_ivy_ng_module(name = name, tsconfig = tsconfig, **kwargs)

View File

@ -186,6 +186,9 @@ export interface ContentChildrenDecorator {
}): Query;
}
/** @experimental */
export declare function createInjector(defType: any, parent?: Injector | null): Injector;
/** @experimental */
export declare function createPlatform(injector: Injector): PlatformRef;
@ -263,6 +266,13 @@ export declare function defineInjectable<T>(opts: {
factory: () => T;
}): InjectableDef<T>;
/** @experimental */
export declare function defineInjector(options: {
factory: () => any;
providers?: any[];
imports?: any[];
}): InjectorDef<any>;
/** @experimental */
export declare function destroyPlatform(): void;
@ -380,6 +390,12 @@ export interface InjectableDecorator {
} & InjectableProvider): Injectable;
}
/** @experimental */
export interface InjectableDef<T> {
factory: () => T;
providedIn: InjectorType<any> | 'root' | 'any' | null;
}
/** @experimental */
export declare type InjectableProvider = ValueSansProvider | ExistingSansProvider | StaticClassSansProvider | ConstructorSansProvider | FactorySansProvider | ClassSansProvider;
@ -418,6 +434,7 @@ export declare abstract class Injector {
/** @deprecated */ abstract get(token: any, notFoundValue?: any): any;
static NULL: Injector;
static THROW_IF_NOT_FOUND: Object;
static ngInjectableDef: InjectableDef<Injector>;
/** @deprecated */ static create(providers: StaticProvider[], parent?: Injector): Injector;
static create(options: {
providers: StaticProvider[];
@ -426,6 +443,27 @@ export declare abstract class Injector {
}): Injector;
}
/** @experimental */
export declare const INJECTOR: InjectionToken<Injector>;
/** @experimental */
export interface InjectorDef<T> {
factory: () => T;
imports: (InjectorType<any> | InjectorTypeWithProviders<any>)[];
providers: (Type<any> | ValueProvider | ExistingProvider | FactoryProvider | ConstructorProvider | StaticClassProvider | ClassProvider | any[])[];
}
/** @experimental */
export interface InjectorType<T> extends Type<T> {
ngInjectorDef: InjectorDef<T>;
}
/** @experimental */
export interface InjectorTypeWithProviders<T> {
ngModule: InjectorType<T>;
providers?: (Type<any> | ValueProvider | ExistingProvider | FactoryProvider | ConstructorProvider | StaticClassProvider | ClassProvider | any[])[];
}
/** @stable */
export declare const Input: InputDecorator;