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": { "master": {
"uncompressed": { "uncompressed": {
"inline": 1447, "inline": 1447,
"main": 155112, "main": 157654,
"polyfills": 59179 "polyfills": 59179
} }
} }
@ -11,7 +11,7 @@
"hello_world__closure": { "hello_world__closure": {
"master": { "master": {
"uncompressed": { "uncompressed": {
"bundle": 105779 "bundle": 106550
} }
} }
}, },

View File

@ -79,7 +79,13 @@ def _expected_outs(ctx):
i18n_messages = i18n_messages_files, 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): 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) outs = _expected_outs(ctx)
if "devmode_manifest" in kwargs: if "devmode_manifest" in kwargs:
expected_outs = outs.devmode_js + outs.declarations + outs.summaries expected_outs = outs.devmode_js + outs.declarations + outs.summaries
@ -92,6 +98,7 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
"generateCodeForLibraries": False, "generateCodeForLibraries": False,
"allowEmptyCodegenFiles": True, "allowEmptyCodegenFiles": True,
"enableSummariesForJit": True, "enableSummariesForJit": True,
"enableIvy": enable_ivy,
"fullTemplateTypeCheck": ctx.attr.type_check, "fullTemplateTypeCheck": ctx.attr.type_check,
# FIXME: wrong place to de-dupe # FIXME: wrong place to de-dupe
"expectedOut": depset([o.path for o in expected_outs]).to_list() "expectedOut": depset([o.path for o in expected_outs]).to_list()
@ -283,7 +290,7 @@ def _write_bundle_index(ctx):
) )
return outputs 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. """Implementation function for the ng_module rule.
This is exposed so that google3 can have its own entry point that re-uses this 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: Args:
ctx: the skylark rule context ctx: the skylark rule context
ts_compile_actions: generates all the actions to run an ngc compilation ts_compile_actions: generates all the actions to run an ngc compilation
ivy: if True, run the compiler in Ivy mode (internal only)
Returns: Returns:
the result of the ng_module rule as a dict, suitable for the result of the ng_module rule as a dict, suitable for
conversion by ts_providers_dict_to_struct conversion by ts_providers_dict_to_struct
""" """
tsconfig = _ngc_tsconfig if not ivy else _ivy_tsconfig
providers = ts_compile_actions( providers = ts_compile_actions(
ctx, is_library=True, compile_action=_prodmode_compile_action, ctx, is_library=True, compile_action=_prodmode_compile_action,
devmode_compile_action=_devmode_compile_action, devmode_compile_action=_devmode_compile_action,
tsc_wrapped_tsconfig=_ngc_tsconfig, tsc_wrapped_tsconfig=tsconfig,
outputs = _ts_expected_outs) outputs = _ts_expected_outs)
outs = _expected_outs(ctx) outs = _expected_outs(ctx)
@ -325,6 +335,9 @@ def ng_module_impl(ctx, ts_compile_actions):
def _ng_module_impl(ctx): def _ng_module_impl(ctx):
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts)) 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 = { NG_MODULE_ATTRIBUTES = {
"srcs": attr.label_list(allow_files = [".ts"]), "srcs": attr.label_list(allow_files = [".ts"]),
@ -363,24 +376,35 @@ NG_MODULE_ATTRIBUTES = {
"_supports_workers": attr.bool(default = True), "_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( ng_module = rule(
implementation = _ng_module_impl, implementation = _ng_module_impl,
attrs = dict(dict(COMMON_ATTRIBUTES, **NG_MODULE_ATTRIBUTES), **{ attrs = NG_MODULE_RULE_ATTRS,
"tsconfig": attr.label(allow_files = True, single_file = True), outputs = COMMON_OUTPUTS,
)
# @// is special syntax for the "main" repository
# The default assumes the user specified a target "node_modules" in their # TODO(alxhub): this rule exists to allow early testing of the Ivy compiler within angular/angular,
# root BUILD file. # and should not be made public. When ng_module() supports Ivy-mode outputs, this rule should be
"node_modules": attr.label( # removed and its usages refactored to use ng_module() directly.
default = Label("@//:node_modules") internal_ivy_ng_module = rule(
), implementation = _ivy_module_impl,
attrs = NG_MODULE_RULE_ATTRS,
"entry_point": attr.string(),
"_index_bundler": attr.label(
executable = True,
cfg = "host",
default = Label("//packages/bazel/src:index_bundler")),
}),
outputs = COMMON_OUTPUTS, 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; requests: RequestLocationMap;
} }
function shouldLower(node: ts.Node | undefined): boolean { function isEligibleForLowering(node: ts.Node | undefined): boolean {
if (node) { if (node) {
switch (node.kind) { switch (node.kind) {
case ts.SyntaxKind.SourceFile: case ts.SyntaxKind.SourceFile:
@ -226,7 +226,7 @@ function shouldLower(node: ts.Node | undefined): boolean {
// Avoid lowering expressions already in an exported variable declaration // Avoid lowering expressions already in an exported variable declaration
return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) == 0; return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) == 0;
} }
return shouldLower(node.parent); return isEligibleForLowering(node.parent);
} }
return true; return true;
} }
@ -251,11 +251,14 @@ function isLiteralFieldNamed(node: ts.Node, names: Set<string>): boolean {
return false; return false;
} }
const LOWERABLE_FIELD_NAMES = new Set(['useValue', 'useFactory', 'data']);
export class LowerMetadataTransform implements RequestsMap, MetadataTransformer { export class LowerMetadataTransform implements RequestsMap, MetadataTransformer {
private cache: MetadataCache; private cache: MetadataCache;
private requests = new Map<string, RequestLocationMap>(); private requests = new Map<string, RequestLocationMap>();
private lowerableFieldNames: Set<string>;
constructor(lowerableFieldNames: string[]) {
this.lowerableFieldNames = new Set<string>(lowerableFieldNames);
}
// RequestMap // RequestMap
getRequests(sourceFile: ts.SourceFile): RequestLocationMap { getRequests(sourceFile: ts.SourceFile): RequestLocationMap {
@ -312,17 +315,46 @@ export class LowerMetadataTransform implements RequestsMap, MetadataTransformer
return false; 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 => { return (value: MetadataValue, node: ts.Node): MetadataValue => {
if (!isPrimitive(value) && !isRewritten(value)) { if (!isPrimitive(value) && !isRewritten(value) && isLowerable(node)) {
if ((node.kind === ts.SyntaxKind.ArrowFunction || return replaceNode(node);
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);
}
} }
return value; return value;
}; };

View File

@ -7,7 +7,7 @@
* found in the LICENSE file at https://angular.io/license * 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 fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
@ -22,9 +22,11 @@ import {LowerMetadataTransform, getExpressionLoweringTransformFactory} from './l
import {MetadataCache, MetadataTransformer} from './metadata_cache'; import {MetadataCache, MetadataTransformer} from './metadata_cache';
import {getAngularEmitterTransformFactory} from './node_emitter_transform'; import {getAngularEmitterTransformFactory} from './node_emitter_transform';
import {PartialModuleMetadataTransformer} from './r3_metadata_transform'; import {PartialModuleMetadataTransformer} from './r3_metadata_transform';
import {StripDecoratorsMetadataTransformer, getDecoratorStripTransformerFactory} from './r3_strip_decorators';
import {getAngularClassTransformerFactory} from './r3_transform'; import {getAngularClassTransformerFactory} from './r3_transform';
import {DTS, GENERATED_FILES, StructureIsReused, TS, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util'; import {DTS, GENERATED_FILES, StructureIsReused, TS, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util';
// Closure compiler transforms the form `Service.ngInjectableDef = X` into // Closure compiler transforms the form `Service.ngInjectableDef = X` into
// `Service$ngInjectableDef = X`. To prevent this transformation, such assignments need to be // `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 // 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; 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 = { const emptyModules: NgAnalyzedModules = {
ngModules: [], ngModules: [],
ngModuleByPipeOrDirective: new Map(), ngModuleByPipeOrDirective: new Map(),
@ -96,6 +117,7 @@ class AngularCompilerProgram implements Program {
private _structuralDiagnostics: Diagnostic[]|undefined; private _structuralDiagnostics: Diagnostic[]|undefined;
private _programWithStubs: ts.Program|undefined; private _programWithStubs: ts.Program|undefined;
private _optionsDiagnostics: Diagnostic[] = []; private _optionsDiagnostics: Diagnostic[] = [];
private _reifiedDecorators: Set<StaticSymbol>;
constructor( constructor(
rootNames: ReadonlyArray<string>, private options: CompilerOptions, rootNames: ReadonlyArray<string>, private options: CompilerOptions,
@ -129,7 +151,9 @@ class AngularCompilerProgram implements Program {
this.host = bundleHost; this.host = bundleHost;
} }
} }
this.loweringMetadataTransform = new LowerMetadataTransform();
this.loweringMetadataTransform =
new LowerMetadataTransform(options.enableIvy ? R3_LOWER_FIELDS : LOWER_FIELDS);
this.metadataCache = this.createMetadataCache([this.loweringMetadataTransform]); this.metadataCache = this.createMetadataCache([this.loweringMetadataTransform]);
} }
@ -269,7 +293,11 @@ class AngularCompilerProgram implements Program {
0) { 0) {
return {emitSkipped: true, diagnostics: [], emittedFiles: []}; 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 = const writeTsFile: ts.WriteFileCallback =
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => { (outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
@ -285,7 +313,8 @@ class AngularCompilerProgram implements Program {
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS; const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
const tsCustomTransformers = this.calculateTransforms( const tsCustomTransformers = this.calculateTransforms(
/* genFiles */ undefined, /* partialModules */ modules, customTransformers); /* genFiles */ undefined, /* partialModules */ modules,
/* stripDecorators */ this.reifiedDecorators, customTransformers);
const emitResult = emitCallback({ const emitResult = emitCallback({
program: this.tsProgram, program: this.tsProgram,
@ -356,8 +385,8 @@ class AngularCompilerProgram implements Program {
const modules = this._analyzedInjectables && const modules = this._analyzedInjectables &&
this.compiler.emitAllPartialModules2(this._analyzedInjectables); this.compiler.emitAllPartialModules2(this._analyzedInjectables);
const tsCustomTransformers = const tsCustomTransformers = this.calculateTransforms(
this.calculateTransforms(genFileByFileName, modules, customTransformers); genFileByFileName, modules, /* stripDecorators */ undefined, customTransformers);
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS; const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
// Restore the original references before we emit so TypeScript doesn't emit // Restore the original references before we emit so TypeScript doesn't emit
// a reference to the .d.ts file. // a reference to the .d.ts file.
@ -512,8 +541,18 @@ class AngularCompilerProgram implements Program {
return this._tsProgram !; 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( private calculateTransforms(
genFiles: Map<string, GeneratedFile>|undefined, partialModules: PartialModule[]|undefined, genFiles: Map<string, GeneratedFile>|undefined, partialModules: PartialModule[]|undefined,
stripDecorators: Set<StaticSymbol>|undefined,
customTransformers?: CustomTransformers): ts.CustomTransformers { customTransformers?: CustomTransformers): ts.CustomTransformers {
const beforeTs: Array<ts.TransformerFactory<ts.SourceFile>> = []; const beforeTs: Array<ts.TransformerFactory<ts.SourceFile>> = [];
const metadataTransforms: MetadataTransformer[] = []; const metadataTransforms: MetadataTransformer[] = [];
@ -521,6 +560,7 @@ class AngularCompilerProgram implements Program {
beforeTs.push(getInlineResourcesTransformFactory(this.tsProgram, this.hostAdapter)); beforeTs.push(getInlineResourcesTransformFactory(this.tsProgram, this.hostAdapter));
metadataTransforms.push(new InlineResourcesMetadataTransformer(this.hostAdapter)); metadataTransforms.push(new InlineResourcesMetadataTransformer(this.hostAdapter));
} }
if (!this.options.disableExpressionLowering) { if (!this.options.disableExpressionLowering) {
beforeTs.push( beforeTs.push(
getExpressionLoweringTransformFactory(this.loweringMetadataTransform, this.tsProgram)); getExpressionLoweringTransformFactory(this.loweringMetadataTransform, this.tsProgram));
@ -536,6 +576,14 @@ class AngularCompilerProgram implements Program {
// the partial module transforms. // the partial module transforms.
metadataTransforms.push(new PartialModuleMetadataTransformer(partialModules)); 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) { if (customTransformers && customTransformers.beforeTs) {
beforeTs.push(...customTransformers.beforeTs); beforeTs.push(...customTransformers.beforeTs);
} }
@ -832,6 +880,7 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
preserveWhitespaces: options.preserveWhitespaces, preserveWhitespaces: options.preserveWhitespaces,
fullTemplateTypeCheck: options.fullTemplateTypeCheck, fullTemplateTypeCheck: options.fullTemplateTypeCheck,
allowEmptyCodegenFiles: options.allowEmptyCodegenFiles, 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\)\);/); 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 {MetadataCache} from '../../src/transformers/metadata_cache';
import {Directory, MockAotContext, MockCompilerHost} from '../mocks'; import {Directory, MockAotContext, MockCompilerHost} from '../mocks';
const DEFAULT_FIELDS_TO_LOWER = ['useFactory', 'useValue', 'data'];
describe('Expression lowering', () => { describe('Expression lowering', () => {
describe('transform', () => { describe('transform', () => {
it('should be able to lower a simple expression', () => { 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', () => { it('should throw a validation exception for invalid files', () => {
const cache = new MetadataCache( const cache = new MetadataCache(
new MetadataCollector({}), /* strict */ true, [new LowerMetadataTransform()]); new MetadataCollector({}), /* strict */ true,
[new LowerMetadataTransform(DEFAULT_FIELDS_TO_LOWER)]);
const sourceFile = ts.createSourceFile( const sourceFile = ts.createSourceFile(
'foo.ts', ` 'foo.ts', `
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
@ -129,7 +132,8 @@ describe('Expression lowering', () => {
it('should not report validation errors on a .d.ts file', () => { it('should not report validation errors on a .d.ts file', () => {
const cache = new MetadataCache( const cache = new MetadataCache(
new MetadataCollector({}), /* strict */ true, [new LowerMetadataTransform()]); new MetadataCollector({}), /* strict */ true,
[new LowerMetadataTransform(DEFAULT_FIELDS_TO_LOWER)]);
const dtsFile = ts.createSourceFile( const dtsFile = ts.createSourceFile(
'foo.d.ts', ` 'foo.d.ts', `
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
@ -244,7 +248,7 @@ function normalizeResult(result: string): string {
function collect(annotatedSource: string) { function collect(annotatedSource: string) {
const {annotations, unannotatedSource} = getAnnotations(annotatedSource); 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 cache = new MetadataCache(new MetadataCollector({}), false, [transformer]);
const sourceFile = ts.createSourceFile( const sourceFile = ts.createSourceFile(
'someName.ts', unannotatedSource, ts.ScriptTarget.Latest, /* setParentNodes */ true); 'someName.ts', unannotatedSource, ts.ScriptTarget.Latest, /* setParentNodes */ true);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@ import * as o from './output/output_ast';
export abstract class CompileReflector { export abstract class CompileReflector {
abstract parameters(typeOrFunc: /*Type*/ any): any[][]; abstract parameters(typeOrFunc: /*Type*/ any): any[][];
abstract annotations(typeOrFunc: /*Type*/ any): any[]; abstract annotations(typeOrFunc: /*Type*/ any): any[];
abstract shallowAnnotations(typeOrFunc: /*Type*/ any): any[];
abstract tryAnnotations(typeOrFunc: /*Type*/ any): any[]; abstract tryAnnotations(typeOrFunc: /*Type*/ any): any[];
abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]}; abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]};
abstract hasLifecycleHook(type: any, lcProperty: string): boolean; 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 inject: o.ExternalReference = {name: 'inject', moduleName: CORE};
static INJECTOR: o.ExternalReference = {name: 'INJECTOR', moduleName: CORE};
static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE}; static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE};
static defineInjectable: o.ExternalReference = {name: 'defineInjectable', moduleName: CORE}; static defineInjectable: o.ExternalReference = {name: 'defineInjectable', moduleName: CORE};
static ViewEncapsulation: o.ExternalReference = { static ViewEncapsulation: o.ExternalReference = {
@ -88,7 +89,6 @@ export class Identifiers {
static inlineInterpolate: o.ExternalReference = { static inlineInterpolate: o.ExternalReference = {
name: 'ɵinlineInterpolate', name: 'ɵinlineInterpolate',
moduleName: CORE, moduleName: CORE,
}; };
static interpolate: o.ExternalReference = {name: 'ɵinterpolate', moduleName: CORE}; static interpolate: o.ExternalReference = {name: 'ɵinterpolate', moduleName: CORE};
static EMPTY_ARRAY: o.ExternalReference = {name: 'ɵEMPTY_ARRAY', 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 * 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 {CompileInjectableMetadata, CompileNgModuleMetadata, CompileProviderMetadata, identifierName} from './compile_metadata';
import {CompileReflector} from './compile_reflector'; import {CompileReflector} from './compile_reflector';
import {InjectFlags, NodeFlags} from './core'; import {InjectFlags, NodeFlags} from './core';
@ -29,7 +30,10 @@ function mapEntry(key: string, value: o.Expression): MapEntry {
} }
export class InjectableCompiler { 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[] { private depsArray(deps: any[], ctx: OutputContext): o.Expression[] {
return deps.map(dep => { 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) { if (flags !== InjectFlags.Default || defaultValue !== undefined) {
args = [tokenExpr, o.literal(defaultValue), o.literal(flags)]; args = [tokenExpr, o.literal(defaultValue), o.literal(flags)];
} else { } 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; let retValue: o.Expression;
if (injectable.useExisting) { if (injectable.useExisting) {
retValue = o.importExpr(Identifiers.inject).callFn([ctx.importExpr(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 { injectableDef(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression {
let providedIn: o.Expression = o.NULL_EXPR; let providedIn: o.Expression = o.NULL_EXPR;
if (injectable.providedIn) { if (injectable.providedIn !== undefined) {
if (typeof injectable.providedIn === 'string') { if (injectable.providedIn === null) {
providedIn = o.NULL_EXPR;
} else if (typeof injectable.providedIn === 'string') {
providedIn = o.literal(injectable.providedIn); providedIn = o.literal(injectable.providedIn);
} else { } else {
providedIn = ctx.importExpr(injectable.providedIn); providedIn = ctx.importExpr(injectable.providedIn);
@ -106,7 +121,7 @@ export class InjectableCompiler {
} }
compile(injectable: CompileInjectableMetadata, ctx: OutputContext): void { compile(injectable: CompileInjectableMetadata, ctx: OutputContext): void {
if (injectable.providedIn) { if (this.alwaysGenerateDef || injectable.providedIn !== undefined) {
const className = identifierName(injectable.type) !; const className = identifierName(injectable.type) !;
const clazz = new o.ClassStmt( const clazz = new o.ClassStmt(
className, null, className, null,

View File

@ -12,9 +12,9 @@ import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata'; import * as cpl from './compile_metadata';
import {CompileReflector} from './compile_reflector'; import {CompileReflector} from './compile_reflector';
import {CompilerConfig} from './config'; 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 {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveResolver} from './directive_resolver'; import {DirectiveResolver, findLast} from './directive_resolver';
import {Identifiers} from './identifiers'; import {Identifiers} from './identifiers';
import {getAllLifecycleHooks} from './lifecycle_reflector'; import {getAllLifecycleHooks} from './lifecycle_reflector';
import {HtmlParser} from './ml_parser/html_parser'; import {HtmlParser} from './ml_parser/html_parser';
@ -44,6 +44,7 @@ export class CompileMetadataResolver {
private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>(); private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>();
private _ngModuleCache = new Map<Type, cpl.CompileNgModuleMetadata>(); private _ngModuleCache = new Map<Type, cpl.CompileNgModuleMetadata>();
private _ngModuleOfTypes = new Map<Type, Type>(); private _ngModuleOfTypes = new Map<Type, Type>();
private _shallowModuleCache = new Map<Type, cpl.CompileShallowModuleMetadata>();
constructor( constructor(
private _config: CompilerConfig, private _htmlParser: HtmlParser, private _config: CompilerConfig, private _htmlParser: HtmlParser,
@ -477,6 +478,26 @@ export class CompileMetadataResolver {
return Promise.all(loading); 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( getNgModuleMetadata(
moduleType: any, throwIfNotFound = true, moduleType: any, throwIfNotFound = true,
alreadyCollecting: Set<any>|null = null): cpl.CompileNgModuleMetadata|null { 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, moduleName: CORE,
}; };
static defineInjector: o.ExternalReference = {
name: 'defineInjector',
moduleName: CORE,
};
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE}; static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
static query: o.ExternalReference = {name: 'ɵQ', 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 * 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 {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
export {Injectable, InjectableDecorator, InjectableProvider} from './di/injectable';
export {inject, InjectFlags, Injector} from './di/injector'; export {inject, InjectFlags, INJECTOR, Injector} from './di/injector';
export {ReflectiveInjector} from './di/reflective_injector'; export {ReflectiveInjector} from './di/reflective_injector';
export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider'; 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 {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
export {ReflectiveKey} from './di/reflective_key'; export {ReflectiveKey} from './di/reflective_key';
export {InjectionToken} from './di/injection_token'; 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 {makeDecorator, makeParamDecorator} from '../util/decorators';
import {getClosureSafeProperty} from '../util/property'; import {getClosureSafeProperty} from '../util/property';
import {InjectableDef, InjectableType, defineInjectable} from './defs';
import {inject, injectArgs} from './injector'; import {inject, injectArgs} from './injector';
import {ClassSansProvider, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, StaticClassProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './provider'; 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. * Injectable decorator and metadata.
* *
@ -132,21 +117,16 @@ export function defineInjectable<T>(opts: {
*/ */
export const Injectable: InjectableDecorator = makeDecorator( export const Injectable: InjectableDecorator = makeDecorator(
'Injectable', undefined, undefined, undefined, 'Injectable', undefined, undefined, undefined,
(injectableType: Type<any>, (injectableType: InjectableType<any>,
options: {providedIn?: Type<any>| 'root' | null} & InjectableProvider) => { options: {providedIn?: Type<any>| 'root' | null} & InjectableProvider) => {
if (options && options.providedIn) { if (options && options.providedIn !== undefined) {
(injectableType as InjectableType<any>).ngInjectableDef = defineInjectable({ injectableType.ngInjectableDef = defineInjectable({
providedIn: options.providedIn, providedIn: options.providedIn,
factory: convertInjectableProviderToFactory(injectableType, options) factory: convertInjectableProviderToFactory(injectableType, options)
}); });
} }
}); });
export interface InjectableDef<T> {
providedIn: Type<any>|'root'|null;
factory: () => T;
}
/** /**
* Type representing injectable service. * Type representing injectable service.
* *

View File

@ -8,7 +8,7 @@
import {Type} from '../type'; 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. * 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. * // 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 * ### Example
* *
* #### Tree-shakeable InjectionToken
*
* {@example core/di/ts/injector_spec.ts region='ShakeableInjectionToken'}
*
* #### Plain InjectionToken
*
* {@example core/di/ts/injector_spec.ts region='InjectionToken'} * {@example core/di/ts/injector_spec.ts region='InjectionToken'}
* *
* @stable * @stable
@ -54,3 +70,7 @@ export class InjectionToken<T> {
toString(): string { return `InjectionToken ${this._desc}`; } 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 {Type} from '../type';
import {stringify} from '../util'; import {stringify} from '../util';
import {InjectableDef, defineInjectable} from './defs';
import {resolveForwardRef} from './forward_ref'; import {resolveForwardRef} from './forward_ref';
import {InjectionToken} from './injection_token'; import {InjectionToken} from './injection_token';
import {Inject, Optional, Self, SkipSelf} from './metadata'; import {Inject, Optional, Self, SkipSelf} from './metadata';
@ -17,7 +19,17 @@ export const SOURCE = '__source';
const _THROW_IF_NOT_FOUND = new Object(); const _THROW_IF_NOT_FOUND = new Object();
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND; 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 { get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
if (notFoundValue === _THROW_IF_NOT_FOUND) { if (notFoundValue === _THROW_IF_NOT_FOUND) {
throw new Error(`NullInjectorError: No provider for ${stringify(token)}!`); throw new Error(`NullInjectorError: No provider for ${stringify(token)}!`);
@ -48,7 +60,7 @@ class _NullInjector implements Injector {
*/ */
export abstract class Injector { export abstract class Injector {
static THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND; 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. * 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); 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); return Array.prototype.slice.call(arguments);
}; };
const GET_PROPERTY_NAME = {} as any; const GET_PROPERTY_NAME = {} as any;
const USE_VALUE = export const USE_VALUE =
getClosureSafeProperty<ValueProvider>({provide: String, useValue: GET_PROPERTY_NAME}); getClosureSafeProperty<ValueProvider>({provide: String, useValue: GET_PROPERTY_NAME});
const NG_TOKEN_PATH = 'ngTokenPath'; const NG_TOKEN_PATH = 'ngTokenPath';
const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath'; const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
@ -127,6 +144,8 @@ export class StaticInjector implements Injector {
const records = this._records = new Map<any, Record>(); const records = this._records = new Map<any, Record>();
records.set( records.set(
Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false}); 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); 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 * 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 {Type} from '../type';
import {TypeDecorator, makeDecorator} from '../util/decorators'; import {TypeDecorator, makeDecorator} from '../util/decorators';
/** /**
* A wrapper around a module that also includes the providers. * A wrapper around a module that also includes the providers.
* *
@ -190,5 +193,17 @@ export interface NgModule {
* @stable * @stable
* @Annotation * @Annotation
*/ */
export const NgModule: NgModuleDecorator = export const NgModule: NgModuleDecorator = makeDecorator(
makeDecorator('NgModule', (ngModule: NgModule) => ngModule); '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 * found in the LICENSE file at https://angular.io/license
*/ */
import {InjectableDef} from '../di/defs';
import {resolveForwardRef} from '../di/forward_ref'; import {resolveForwardRef} from '../di/forward_ref';
import {InjectableDef} from '../di/injectable'; import {INJECTOR, InjectFlags, Injector, setCurrentInjector} from '../di/injector';
import {InjectFlags, Injector, setCurrentInjector} from '../di/injector';
import {APP_ROOT} from '../di/scope'; import {APP_ROOT} from '../di/scope';
import {NgModuleRef} from '../linker/ng_module_factory'; import {NgModuleRef} from '../linker/ng_module_factory';
import {stringify} from '../util'; import {stringify} from '../util';

View File

@ -8,7 +8,8 @@
import {isDevMode} from '../application_ref'; import {isDevMode} from '../application_ref';
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node'; 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 {ErrorHandler} from '../error_handler';
import {ComponentFactory} from '../linker/component_factory'; import {ComponentFactory} from '../linker/component_factory';
import {NgModuleRef} from '../linker/ng_module_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', () => { describe('displayName', () => {
it('should work', () => { it('should work', () => {
expect(Injector.create([Engine.PROVIDER, {provide: BrokenEngine, useValue: null}]).toString()) 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 * 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 * as r3 from '../../../src/render3/index';
import {$pending_pr_22458$} from './pending_api_spec';
const details_elided = { const details_elided = {
type: Object, type: Object,
} as any; } as any;
@ -60,7 +58,7 @@ export class LibBComponent {
@NgModule({declarations: [LibAComponent], imports: []}) @NgModule({declarations: [LibAComponent], imports: []})
export class LibBModule { export class LibBModule {
// COMPILER GENERATED // COMPILER GENERATED
static ngInjectorDef = $pending_pr_22458$.defineInjector(details_elided); static ngInjectorDef = defineInjector(details_elided);
} }
// END FILE: node_modules/libB/module.ts // END FILE: node_modules/libB/module.ts
// BEGIN FILE: node_modules/libB/module.metadata.json // BEGIN FILE: node_modules/libB/module.metadata.json
@ -92,7 +90,7 @@ export class AppComponent {
@NgModule({declarations: [LibAComponent], imports: []}) @NgModule({declarations: [LibAComponent], imports: []})
export class AppModule { export class AppModule {
// COMPILER GENERATED // COMPILER GENERATED
static ngInjectorDef = $pending_pr_22458$.defineInjector(details_elided); static ngInjectorDef = defineInjector(details_elided);
} }
// END FILE: src/app.ts // END FILE: src/app.ts
@ -113,7 +111,7 @@ function ngBackPatch_node_modules_libB_module_LibAComponent() {
} }
function ngBackPatch_node_modules_libB_module_LibAModule() { 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} = { export const AppModuleFactory: NgModuleFactory<AppModule>&{patchedDeps: boolean} = {

View File

@ -6,12 +6,10 @@
* found in the LICENSE file at https://angular.io/license * 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 * as $r3$ from '../../../src/core_render3_private_export';
import {renderComponent, toHtml} from '../render_util'; import {renderComponent, toHtml} from '../render_util';
import {$pending_pr_22458$} from './pending_api_spec';
/// See: `normative.md` /// See: `normative.md`
@ -118,7 +116,7 @@ describe('injection', () => {
@Injectable() @Injectable()
class ServiceA { class ServiceA {
// NORMATIVE // NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable({ static ngInjectableDef = defineInjectable({
factory: function ServiceA_Factory() { return new ServiceA(); }, factory: function ServiceA_Factory() { return new ServiceA(); },
}); });
// /NORMATIVE // /NORMATIVE
@ -127,7 +125,7 @@ describe('injection', () => {
@Injectable() @Injectable()
class ServiceB { class ServiceB {
// NORMATIVE // NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable({ static ngInjectableDef = defineInjectable({
factory: function ServiceA_Factory() { return new ServiceB(); }, factory: function ServiceA_Factory() { return new ServiceB(); },
}); });
// /NORMATIVE // /NORMATIVE
@ -146,8 +144,7 @@ describe('injection', () => {
tag: 'my-app', tag: 'my-app',
factory: function MyApp_Factory() { factory: function MyApp_Factory() {
return new MyApp( return new MyApp(
$r3$.ɵdirectiveInject(ServiceA), $r3$.ɵdirectiveInject(ServiceB), $r3$.ɵdirectiveInject(ServiceA), $r3$.ɵdirectiveInject(ServiceB), inject(INJECTOR));
$pending_pr_22458$.injectInjector());
}, },
/** */ /** */
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {}, template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {},
@ -169,10 +166,9 @@ describe('injection', () => {
constructor(@Inject(String) name: String, injector: Injector) {} constructor(@Inject(String) name: String, injector: Injector) {}
// NORMATIVE // NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable({ static ngInjectableDef = defineInjectable({
factory: function ServiceA_Factory() { factory: function ServiceA_Factory() {
return new ServiceA( return new ServiceA(inject(String), inject(INJECTOR));
$pending_pr_22458$.inject(String), $pending_pr_22458$.injectInjector());
}, },
}); });
// /NORMATIVE // /NORMATIVE
@ -182,11 +178,9 @@ describe('injection', () => {
class ServiceB { class ServiceB {
constructor(serviceA: ServiceA, @SkipSelf() injector: Injector) {} constructor(serviceA: ServiceA, @SkipSelf() injector: Injector) {}
// NORMATIVE // NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable({ static ngInjectableDef = defineInjectable({
factory: function ServiceA_Factory() { factory: function ServiceA_Factory() {
return new ServiceB( return new ServiceB(inject(ServiceA), inject(INJECTOR, undefined, InjectFlags.SkipSelf));
$pending_pr_22458$.inject(ServiceA),
$pending_pr_22458$.injectInjector(InjectFlags.SkipSelf));
}, },
}); });
// /NORMATIVE // /NORMATIVE

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license * 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 * as r3 from '../../../src/render3/index';
import {$pending_pr_22458$} from './pending_api_spec';
/** /**
* GOALS: * GOALS:
@ -29,7 +29,7 @@ class ThirdPartyClass {
@Injectable() @Injectable()
class CompiledWithIvy { class CompiledWithIvy {
// NORMATIVE // NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable( static ngInjectableDef = defineInjectable(
{factory: function CompileWithIvy_Factory() { return new CompiledWithIvy(); }}); {factory: function CompileWithIvy_Factory() { return new CompiledWithIvy(); }});
// /NORMATIVE // /NORMATIVE
} }
@ -38,7 +38,7 @@ class CompiledWithIvy {
@NgModule({providers: [ThirdPartyClass, CompiledWithIvy]}) @NgModule({providers: [ThirdPartyClass, CompiledWithIvy]})
class CompiledWithIvyModule { class CompiledWithIvyModule {
// NORMATIVE // NORMATIVE
static ngInjectorDef = $pending_pr_22458$.defineInjector({ static ngInjectorDef = defineInjector({
providers: [ThirdPartyClass, CompiledWithIvy], providers: [ThirdPartyClass, CompiledWithIvy],
factory: function CompiledWithIvyModule_Factory() { return new CompiledWithIvyModule(); } factory: function CompiledWithIvyModule_Factory() { return new CompiledWithIvyModule(); }
}); });
@ -72,7 +72,7 @@ function ngPatch_depsOf_CompiledWithIvyModule() {
} }
function ngPatch_node_modules_some_library_path_public_CompileWithIvy() { function ngPatch_node_modules_some_library_path_public_CompileWithIvy() {
/** @__BUILD_OPTIMIZER_COLOCATE__ */ /** @__BUILD_OPTIMIZER_COLOCATE__ */
(ThirdPartyClass as any).ngInjectableDef = $pending_pr_22458$.defineInjectable( (ThirdPartyClass as any).ngInjectableDef = defineInjectable(
{factory: function CompileWithIvy_Factory() { return new ThirdPartyClass(); }}); {factory: function CompileWithIvy_Factory() { return new ThirdPartyClass(); }});
} }
// /NORMATIVE // /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 {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 {withBody} from '@angular/core/testing';
import * as r3 from '../../../src/render3/index'; import * as r3 from '../../../src/render3/index';
import {$pending_pr_22458$} from './pending_api_spec';
/// See: `normative.md` /// See: `normative.md`
@ -33,7 +32,7 @@ class AppState {
]; ];
// NORMATIVE // NORMATIVE
static ngInjectableDef = $pending_pr_22458$.defineInjectable({factory: () => new AppState()}); static ngInjectableDef = defineInjectable({factory: () => new AppState()});
// /NORMATIVE // /NORMATIVE
} }
@ -158,7 +157,7 @@ const e1_attrs = ['type', 'checkbox'];
}) })
class ToDoAppModule { class ToDoAppModule {
// NORMATIVE // NORMATIVE
static ngInjectorDef = $pending_pr_22458$.defineInjector({ static ngInjectorDef = defineInjector({
factory: () => new ToDoAppModule(), factory: () => new ToDoAppModule(),
providers: [AppState], providers: [AppState],
}); });

View File

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

View File

@ -37,6 +37,9 @@ export class JitReflector implements CompileReflector {
annotations(typeOrFunc: /*Type*/ any): any[] { annotations(typeOrFunc: /*Type*/ any): any[] {
return this.reflectionCapabilities.annotations(typeOrFunc); 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[]} { propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]} {
return this.reflectionCapabilities.propMetadata(typeOrFunc); 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_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("@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: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" DEFAULT_TSCONFIG = "//packages:tsconfig-build.json"
@ -79,3 +80,8 @@ def ts_web_test(bootstrap = [], deps = [], **kwargs):
bootstrap = bootstrap, bootstrap = bootstrap,
deps = local_deps, 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; }): Query;
} }
/** @experimental */
export declare function createInjector(defType: any, parent?: Injector | null): Injector;
/** @experimental */ /** @experimental */
export declare function createPlatform(injector: Injector): PlatformRef; export declare function createPlatform(injector: Injector): PlatformRef;
@ -263,6 +266,13 @@ export declare function defineInjectable<T>(opts: {
factory: () => T; factory: () => T;
}): InjectableDef<T>; }): InjectableDef<T>;
/** @experimental */
export declare function defineInjector(options: {
factory: () => any;
providers?: any[];
imports?: any[];
}): InjectorDef<any>;
/** @experimental */ /** @experimental */
export declare function destroyPlatform(): void; export declare function destroyPlatform(): void;
@ -380,6 +390,12 @@ export interface InjectableDecorator {
} & InjectableProvider): Injectable; } & InjectableProvider): Injectable;
} }
/** @experimental */
export interface InjectableDef<T> {
factory: () => T;
providedIn: InjectorType<any> | 'root' | 'any' | null;
}
/** @experimental */ /** @experimental */
export declare type InjectableProvider = ValueSansProvider | ExistingSansProvider | StaticClassSansProvider | ConstructorSansProvider | FactorySansProvider | ClassSansProvider; 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; /** @deprecated */ abstract get(token: any, notFoundValue?: any): any;
static NULL: Injector; static NULL: Injector;
static THROW_IF_NOT_FOUND: Object; static THROW_IF_NOT_FOUND: Object;
static ngInjectableDef: InjectableDef<Injector>;
/** @deprecated */ static create(providers: StaticProvider[], parent?: Injector): Injector; /** @deprecated */ static create(providers: StaticProvider[], parent?: Injector): Injector;
static create(options: { static create(options: {
providers: StaticProvider[]; providers: StaticProvider[];
@ -426,6 +443,27 @@ export declare abstract class Injector {
}): 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 */ /** @stable */
export declare const Input: InputDecorator; export declare const Input: InputDecorator;