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:
parent
688096b7a3
commit
6ef9f2278f
|
@ -3,7 +3,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"inline": 1447,
|
||||
"main": 155112,
|
||||
"main": 157654,
|
||||
"polyfills": 59179
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
|||
"hello_world__closure": {
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"bundle": 105779
|
||||
"bundle": 106550
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -79,7 +79,13 @@ def _expected_outs(ctx):
|
|||
i18n_messages = i18n_messages_files,
|
||||
)
|
||||
|
||||
def _ivy_tsconfig(ctx, files, srcs, **kwargs):
|
||||
return _ngc_tsconfig_helper(ctx, files, srcs, True, **kwargs)
|
||||
|
||||
def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
||||
return _ngc_tsconfig_helper(ctx, files, srcs, False, **kwargs)
|
||||
|
||||
def _ngc_tsconfig_helper(ctx, files, srcs, enable_ivy, **kwargs):
|
||||
outs = _expected_outs(ctx)
|
||||
if "devmode_manifest" in kwargs:
|
||||
expected_outs = outs.devmode_js + outs.declarations + outs.summaries
|
||||
|
@ -92,6 +98,7 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
|||
"generateCodeForLibraries": False,
|
||||
"allowEmptyCodegenFiles": True,
|
||||
"enableSummariesForJit": True,
|
||||
"enableIvy": enable_ivy,
|
||||
"fullTemplateTypeCheck": ctx.attr.type_check,
|
||||
# FIXME: wrong place to de-dupe
|
||||
"expectedOut": depset([o.path for o in expected_outs]).to_list()
|
||||
|
@ -283,7 +290,7 @@ def _write_bundle_index(ctx):
|
|||
)
|
||||
return outputs
|
||||
|
||||
def ng_module_impl(ctx, ts_compile_actions):
|
||||
def ng_module_impl(ctx, ts_compile_actions, ivy = False):
|
||||
"""Implementation function for the ng_module rule.
|
||||
|
||||
This is exposed so that google3 can have its own entry point that re-uses this
|
||||
|
@ -292,16 +299,19 @@ def ng_module_impl(ctx, ts_compile_actions):
|
|||
Args:
|
||||
ctx: the skylark rule context
|
||||
ts_compile_actions: generates all the actions to run an ngc compilation
|
||||
ivy: if True, run the compiler in Ivy mode (internal only)
|
||||
|
||||
Returns:
|
||||
the result of the ng_module rule as a dict, suitable for
|
||||
conversion by ts_providers_dict_to_struct
|
||||
"""
|
||||
|
||||
tsconfig = _ngc_tsconfig if not ivy else _ivy_tsconfig
|
||||
|
||||
providers = ts_compile_actions(
|
||||
ctx, is_library=True, compile_action=_prodmode_compile_action,
|
||||
devmode_compile_action=_devmode_compile_action,
|
||||
tsc_wrapped_tsconfig=_ngc_tsconfig,
|
||||
tsc_wrapped_tsconfig=tsconfig,
|
||||
outputs = _ts_expected_outs)
|
||||
|
||||
outs = _expected_outs(ctx)
|
||||
|
@ -325,6 +335,9 @@ def ng_module_impl(ctx, ts_compile_actions):
|
|||
def _ng_module_impl(ctx):
|
||||
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts))
|
||||
|
||||
def _ivy_module_impl(ctx):
|
||||
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts, True))
|
||||
|
||||
NG_MODULE_ATTRIBUTES = {
|
||||
"srcs": attr.label_list(allow_files = [".ts"]),
|
||||
|
||||
|
@ -363,9 +376,7 @@ NG_MODULE_ATTRIBUTES = {
|
|||
"_supports_workers": attr.bool(default = True),
|
||||
}
|
||||
|
||||
ng_module = rule(
|
||||
implementation = _ng_module_impl,
|
||||
attrs = dict(dict(COMMON_ATTRIBUTES, **NG_MODULE_ATTRIBUTES), **{
|
||||
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
|
||||
|
@ -381,6 +392,19 @@ ng_module = rule(
|
|||
executable = True,
|
||||
cfg = "host",
|
||||
default = Label("//packages/bazel/src:index_bundler")),
|
||||
}),
|
||||
})
|
||||
|
||||
ng_module = rule(
|
||||
implementation = _ng_module_impl,
|
||||
attrs = NG_MODULE_RULE_ATTRS,
|
||||
outputs = COMMON_OUTPUTS,
|
||||
)
|
||||
|
||||
# TODO(alxhub): this rule exists to allow early testing of the Ivy compiler within angular/angular,
|
||||
# and should not be made public. When ng_module() supports Ivy-mode outputs, this rule should be
|
||||
# removed and its usages refactored to use ng_module() directly.
|
||||
internal_ivy_ng_module = rule(
|
||||
implementation = _ivy_module_impl,
|
||||
attrs = NG_MODULE_RULE_ATTRS,
|
||||
outputs = COMMON_OUTPUTS,
|
||||
)
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
|
@ -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 {
|
||||
}
|
|
@ -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",
|
||||
],
|
||||
)
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -208,7 +208,7 @@ interface MetadataAndLoweringRequests {
|
|||
requests: RequestLocationMap;
|
||||
}
|
||||
|
||||
function shouldLower(node: ts.Node | undefined): boolean {
|
||||
function isEligibleForLowering(node: ts.Node | undefined): boolean {
|
||||
if (node) {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.SourceFile:
|
||||
|
@ -226,7 +226,7 @@ function shouldLower(node: ts.Node | undefined): boolean {
|
|||
// Avoid lowering expressions already in an exported variable declaration
|
||||
return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) == 0;
|
||||
}
|
||||
return shouldLower(node.parent);
|
||||
return isEligibleForLowering(node.parent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -251,11 +251,14 @@ function isLiteralFieldNamed(node: ts.Node, names: Set<string>): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
const LOWERABLE_FIELD_NAMES = new Set(['useValue', 'useFactory', 'data']);
|
||||
|
||||
export class LowerMetadataTransform implements RequestsMap, MetadataTransformer {
|
||||
private cache: MetadataCache;
|
||||
private requests = new Map<string, RequestLocationMap>();
|
||||
private lowerableFieldNames: Set<string>;
|
||||
|
||||
constructor(lowerableFieldNames: string[]) {
|
||||
this.lowerableFieldNames = new Set<string>(lowerableFieldNames);
|
||||
}
|
||||
|
||||
// RequestMap
|
||||
getRequests(sourceFile: ts.SourceFile): RequestLocationMap {
|
||||
|
@ -312,17 +315,46 @@ export class LowerMetadataTransform implements RequestsMap, MetadataTransformer
|
|||
return false;
|
||||
};
|
||||
|
||||
return (value: MetadataValue, node: ts.Node): MetadataValue => {
|
||||
if (!isPrimitive(value) && !isRewritten(value)) {
|
||||
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) &&
|
||||
shouldLower(node)) {
|
||||
return replaceNode(node);
|
||||
}
|
||||
if (isLiteralFieldNamed(node, LOWERABLE_FIELD_NAMES) && shouldLower(node) &&
|
||||
isEligibleForLowering(node)) {
|
||||
lowerable = true;
|
||||
} else if (
|
||||
isLiteralFieldNamed(node, this.lowerableFieldNames) && isEligibleForLowering(node) &&
|
||||
!isExportedSymbol(node) && !isExportedPropertyAccess(node)) {
|
||||
return replaceNode(node);
|
||||
lowerable = true;
|
||||
}
|
||||
return lowerable;
|
||||
};
|
||||
|
||||
const hasLowerableParent = (node: ts.Node | undefined): boolean => {
|
||||
if (node === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (!hasLowerableParentCache.has(node)) {
|
||||
hasLowerableParentCache.set(
|
||||
node, shouldBeLowered(node.parent) || hasLowerableParent(node.parent));
|
||||
}
|
||||
return hasLowerableParentCache.get(node) !;
|
||||
};
|
||||
|
||||
const isLowerable = (node: ts.Node | undefined): boolean => {
|
||||
if (node === undefined) {
|
||||
return false;
|
||||
}
|
||||
return shouldBeLowered(node) && !hasLowerableParent(node);
|
||||
};
|
||||
|
||||
return (value: MetadataValue, node: ts.Node): MetadataValue => {
|
||||
if (!isPrimitive(value) && !isRewritten(value) && isLowerable(node)) {
|
||||
return replaceNode(node);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, FormattedMessageChain, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedFileWithInjectables, NgAnalyzedModules, ParseSourceSpan, PartialModule, Position, Serializer, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isFormattedError, isSyntaxError} from '@angular/compiler';
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, FormattedMessageChain, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedFileWithInjectables, NgAnalyzedModules, ParseSourceSpan, PartialModule, Position, Serializer, StaticSymbol, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isFormattedError, isSyntaxError} from '@angular/compiler';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
@ -22,9 +22,11 @@ import {LowerMetadataTransform, getExpressionLoweringTransformFactory} from './l
|
|||
import {MetadataCache, MetadataTransformer} from './metadata_cache';
|
||||
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
||||
import {PartialModuleMetadataTransformer} from './r3_metadata_transform';
|
||||
import {StripDecoratorsMetadataTransformer, getDecoratorStripTransformerFactory} from './r3_strip_decorators';
|
||||
import {getAngularClassTransformerFactory} from './r3_transform';
|
||||
import {DTS, GENERATED_FILES, StructureIsReused, TS, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util';
|
||||
|
||||
|
||||
// Closure compiler transforms the form `Service.ngInjectableDef = X` into
|
||||
// `Service$ngInjectableDef = X`. To prevent this transformation, such assignments need to be
|
||||
// annotated with @nocollapse. Unfortunately, a bug in Typescript where comments aren't propagated
|
||||
|
@ -62,6 +64,25 @@ const R3_NOCOLLAPSE_DEFS = '$1\/** @nocollapse *\/ $2';
|
|||
*/
|
||||
const MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT = 20;
|
||||
|
||||
|
||||
/**
|
||||
* Fields to lower within metadata in render2 mode.
|
||||
*/
|
||||
const LOWER_FIELDS = ['useValue', 'useFactory', 'data'];
|
||||
|
||||
/**
|
||||
* Fields to lower within metadata in render3 mode.
|
||||
*/
|
||||
const R3_LOWER_FIELDS = [...LOWER_FIELDS, 'providers', 'imports', 'exports'];
|
||||
|
||||
const R3_REIFIED_DECORATORS = [
|
||||
'Component',
|
||||
'Directive',
|
||||
'Injectable',
|
||||
'NgModule',
|
||||
'Pipe',
|
||||
];
|
||||
|
||||
const emptyModules: NgAnalyzedModules = {
|
||||
ngModules: [],
|
||||
ngModuleByPipeOrDirective: new Map(),
|
||||
|
@ -96,6 +117,7 @@ class AngularCompilerProgram implements Program {
|
|||
private _structuralDiagnostics: Diagnostic[]|undefined;
|
||||
private _programWithStubs: ts.Program|undefined;
|
||||
private _optionsDiagnostics: Diagnostic[] = [];
|
||||
private _reifiedDecorators: Set<StaticSymbol>;
|
||||
|
||||
constructor(
|
||||
rootNames: ReadonlyArray<string>, private options: CompilerOptions,
|
||||
|
@ -129,7 +151,9 @@ class AngularCompilerProgram implements Program {
|
|||
this.host = bundleHost;
|
||||
}
|
||||
}
|
||||
this.loweringMetadataTransform = new LowerMetadataTransform();
|
||||
|
||||
this.loweringMetadataTransform =
|
||||
new LowerMetadataTransform(options.enableIvy ? R3_LOWER_FIELDS : LOWER_FIELDS);
|
||||
this.metadataCache = this.createMetadataCache([this.loweringMetadataTransform]);
|
||||
}
|
||||
|
||||
|
@ -269,7 +293,11 @@ class AngularCompilerProgram implements Program {
|
|||
0) {
|
||||
return {emitSkipped: true, diagnostics: [], emittedFiles: []};
|
||||
}
|
||||
const modules = this.compiler.emitAllPartialModules(this.analyzedModules);
|
||||
|
||||
// analyzedModules and analyzedInjectables are created together. If one exists, so does the
|
||||
// other.
|
||||
const modules =
|
||||
this.compiler.emitAllPartialModules(this.analyzedModules, this._analyzedInjectables !);
|
||||
|
||||
const writeTsFile: ts.WriteFileCallback =
|
||||
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
|
||||
|
@ -285,7 +313,8 @@ class AngularCompilerProgram implements Program {
|
|||
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
|
||||
|
||||
const tsCustomTransformers = this.calculateTransforms(
|
||||
/* genFiles */ undefined, /* partialModules */ modules, customTransformers);
|
||||
/* genFiles */ undefined, /* partialModules */ modules,
|
||||
/* stripDecorators */ this.reifiedDecorators, customTransformers);
|
||||
|
||||
const emitResult = emitCallback({
|
||||
program: this.tsProgram,
|
||||
|
@ -356,8 +385,8 @@ class AngularCompilerProgram implements Program {
|
|||
const modules = this._analyzedInjectables &&
|
||||
this.compiler.emitAllPartialModules2(this._analyzedInjectables);
|
||||
|
||||
const tsCustomTransformers =
|
||||
this.calculateTransforms(genFileByFileName, modules, customTransformers);
|
||||
const tsCustomTransformers = this.calculateTransforms(
|
||||
genFileByFileName, modules, /* stripDecorators */ undefined, customTransformers);
|
||||
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
|
||||
// Restore the original references before we emit so TypeScript doesn't emit
|
||||
// a reference to the .d.ts file.
|
||||
|
@ -512,8 +541,18 @@ class AngularCompilerProgram implements Program {
|
|||
return this._tsProgram !;
|
||||
}
|
||||
|
||||
private get reifiedDecorators(): Set<StaticSymbol> {
|
||||
if (!this._reifiedDecorators) {
|
||||
const reflector = this.compiler.reflector;
|
||||
this._reifiedDecorators = new Set(
|
||||
R3_REIFIED_DECORATORS.map(name => reflector.findDeclaration('@angular/core', name)));
|
||||
}
|
||||
return this._reifiedDecorators;
|
||||
}
|
||||
|
||||
private calculateTransforms(
|
||||
genFiles: Map<string, GeneratedFile>|undefined, partialModules: PartialModule[]|undefined,
|
||||
stripDecorators: Set<StaticSymbol>|undefined,
|
||||
customTransformers?: CustomTransformers): ts.CustomTransformers {
|
||||
const beforeTs: Array<ts.TransformerFactory<ts.SourceFile>> = [];
|
||||
const metadataTransforms: MetadataTransformer[] = [];
|
||||
|
@ -521,6 +560,7 @@ class AngularCompilerProgram implements Program {
|
|||
beforeTs.push(getInlineResourcesTransformFactory(this.tsProgram, this.hostAdapter));
|
||||
metadataTransforms.push(new InlineResourcesMetadataTransformer(this.hostAdapter));
|
||||
}
|
||||
|
||||
if (!this.options.disableExpressionLowering) {
|
||||
beforeTs.push(
|
||||
getExpressionLoweringTransformFactory(this.loweringMetadataTransform, this.tsProgram));
|
||||
|
@ -536,6 +576,14 @@ class AngularCompilerProgram implements Program {
|
|||
// the partial module transforms.
|
||||
metadataTransforms.push(new PartialModuleMetadataTransformer(partialModules));
|
||||
}
|
||||
|
||||
if (stripDecorators) {
|
||||
beforeTs.push(getDecoratorStripTransformerFactory(
|
||||
stripDecorators, this.compiler.reflector, this.getTsProgram().getTypeChecker()));
|
||||
metadataTransforms.push(
|
||||
new StripDecoratorsMetadataTransformer(stripDecorators, this.compiler.reflector));
|
||||
}
|
||||
|
||||
if (customTransformers && customTransformers.beforeTs) {
|
||||
beforeTs.push(...customTransformers.beforeTs);
|
||||
}
|
||||
|
@ -832,6 +880,7 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
|
|||
preserveWhitespaces: options.preserveWhitespaces,
|
||||
fullTemplateTypeCheck: options.fullTemplateTypeCheck,
|
||||
allowEmptyCodegenFiles: options.allowEmptyCodegenFiles,
|
||||
enableIvy: options.enableIvy,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -2205,4 +2205,120 @@ describe('ngc transformer command-line', () => {
|
|||
expect(source).toMatch(/new Service\(i0\.inject\(exports\.TOKEN\)\);/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngInjectorDef', () => {
|
||||
it('is applied with lowered metadata', () => {
|
||||
writeConfig(`{
|
||||
"extends": "./tsconfig-base.json",
|
||||
"files": ["module.ts"],
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": true,
|
||||
"skipTemplateCodegen": true
|
||||
}
|
||||
}`);
|
||||
write('module.ts', `
|
||||
import {Injectable, NgModule} from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class ServiceA {}
|
||||
|
||||
@Injectable()
|
||||
export class ServiceB {}
|
||||
|
||||
@NgModule()
|
||||
export class Exported {}
|
||||
|
||||
@NgModule({
|
||||
providers: [ServiceA]
|
||||
})
|
||||
export class Imported {
|
||||
static forRoot() {
|
||||
console.log('not statically analyzable');
|
||||
return {
|
||||
ngModule: Imported,
|
||||
providers: [] as any,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
providers: [ServiceA, ServiceB],
|
||||
imports: [Imported.forRoot()],
|
||||
exports: [Exported],
|
||||
})
|
||||
export class Module {}
|
||||
`);
|
||||
|
||||
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
const modulePath = path.resolve(outDir, 'module.js');
|
||||
const moduleSource = fs.readFileSync(modulePath, 'utf8');
|
||||
expect(moduleSource)
|
||||
.toContain('var ɵ1 = [ServiceA, ServiceB], ɵ2 = [Imported.forRoot()], ɵ3 = [Exported];');
|
||||
expect(moduleSource)
|
||||
.toContain(
|
||||
'Imported.ngInjectorDef = i0.defineInjector({ factory: function Imported_Factory() { return new Imported(); }, providers: ɵ0, imports: [] });');
|
||||
expect(moduleSource)
|
||||
.toContain(
|
||||
'Module.ngInjectorDef = i0.defineInjector({ factory: function Module_Factory() { return new Module(); }, providers: ɵ1, imports: [ɵ2, ɵ3] });');
|
||||
});
|
||||
|
||||
it('strips decorator in ivy mode', () => {
|
||||
writeConfig(`{
|
||||
"extends": "./tsconfig-base.json",
|
||||
"files": ["service.ts"],
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": true
|
||||
}
|
||||
}`);
|
||||
write('service.ts', `
|
||||
import {Injectable, Self} from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class ServiceA {}
|
||||
|
||||
@Injectable()
|
||||
@Self()
|
||||
export class ServiceB {}
|
||||
`);
|
||||
|
||||
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
const modulePath = path.resolve(outDir, 'service.js');
|
||||
const moduleSource = fs.readFileSync(modulePath, 'utf8');
|
||||
expect(moduleSource).not.toMatch(/ServiceA\.decorators =/);
|
||||
expect(moduleSource).toMatch(/ServiceB\.decorators =/);
|
||||
expect(moduleSource).toMatch(/type: Self/);
|
||||
expect(moduleSource).not.toMatch(/type: Injectable/);
|
||||
});
|
||||
|
||||
it('rewrites Injector to INJECTOR in Ivy factory functions ', () => {
|
||||
writeConfig(`{
|
||||
"extends": "./tsconfig-base.json",
|
||||
"files": ["service.ts"],
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": true
|
||||
}
|
||||
}`);
|
||||
|
||||
write('service.ts', `
|
||||
import {Injectable, Injector} from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class Service {
|
||||
constructor(private injector: Injector) {}
|
||||
}
|
||||
`);
|
||||
|
||||
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
const modulePath = path.resolve(outDir, 'service.js');
|
||||
const moduleSource = fs.readFileSync(modulePath, 'utf8');
|
||||
expect(moduleSource).not.toMatch(/inject\(i0\.Injector/);
|
||||
expect(moduleSource).toMatch(/inject\(i0\.INJECTOR/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,6 +13,8 @@ import {LowerMetadataTransform, LoweringRequest, RequestLocationMap, getExpressi
|
|||
import {MetadataCache} from '../../src/transformers/metadata_cache';
|
||||
import {Directory, MockAotContext, MockCompilerHost} from '../mocks';
|
||||
|
||||
const DEFAULT_FIELDS_TO_LOWER = ['useFactory', 'useValue', 'data'];
|
||||
|
||||
describe('Expression lowering', () => {
|
||||
describe('transform', () => {
|
||||
it('should be able to lower a simple expression', () => {
|
||||
|
@ -112,7 +114,8 @@ describe('Expression lowering', () => {
|
|||
|
||||
it('should throw a validation exception for invalid files', () => {
|
||||
const cache = new MetadataCache(
|
||||
new MetadataCollector({}), /* strict */ true, [new LowerMetadataTransform()]);
|
||||
new MetadataCollector({}), /* strict */ true,
|
||||
[new LowerMetadataTransform(DEFAULT_FIELDS_TO_LOWER)]);
|
||||
const sourceFile = ts.createSourceFile(
|
||||
'foo.ts', `
|
||||
import {Injectable} from '@angular/core';
|
||||
|
@ -129,7 +132,8 @@ describe('Expression lowering', () => {
|
|||
|
||||
it('should not report validation errors on a .d.ts file', () => {
|
||||
const cache = new MetadataCache(
|
||||
new MetadataCollector({}), /* strict */ true, [new LowerMetadataTransform()]);
|
||||
new MetadataCollector({}), /* strict */ true,
|
||||
[new LowerMetadataTransform(DEFAULT_FIELDS_TO_LOWER)]);
|
||||
const dtsFile = ts.createSourceFile(
|
||||
'foo.d.ts', `
|
||||
import {Injectable} from '@angular/core';
|
||||
|
@ -244,7 +248,7 @@ function normalizeResult(result: string): string {
|
|||
|
||||
function collect(annotatedSource: string) {
|
||||
const {annotations, unannotatedSource} = getAnnotations(annotatedSource);
|
||||
const transformer = new LowerMetadataTransform();
|
||||
const transformer = new LowerMetadataTransform(DEFAULT_FIELDS_TO_LOWER);
|
||||
const cache = new MetadataCache(new MetadataCollector({}), false, [transformer]);
|
||||
const sourceFile = ts.createSourceFile(
|
||||
'someName.ts', unannotatedSource, ts.ScriptTarget.Latest, /* setParentNodes */ true);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileShallowModuleMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {ConstantPool} from '../constant_pool';
|
||||
import {ViewEncapsulation} from '../core';
|
||||
|
@ -20,6 +20,7 @@ import {NgModuleCompiler} from '../ng_module_compiler';
|
|||
import {OutputEmitter} from '../output/abstract_emitter';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ParseError} from '../parse_util';
|
||||
import {compileNgModule as compileIvyModule} from '../render3/r3_module_compiler';
|
||||
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
||||
import {OutputMode} from '../render3/r3_types';
|
||||
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
||||
|
@ -57,7 +58,7 @@ export class AotCompiler {
|
|||
|
||||
constructor(
|
||||
private _config: CompilerConfig, private _options: AotCompilerOptions,
|
||||
private _host: AotCompilerHost, private _reflector: StaticReflector,
|
||||
private _host: AotCompilerHost, readonly reflector: StaticReflector,
|
||||
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _typeCheckCompiler: TypeCheckCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
||||
|
@ -283,7 +284,7 @@ export class AotCompiler {
|
|||
private _externalIdentifierReferences(references: o.ExternalReference[]): StaticSymbol[] {
|
||||
const result: StaticSymbol[] = [];
|
||||
for (let reference of references) {
|
||||
const token = createTokenForExternalReference(this._reflector, reference);
|
||||
const token = createTokenForExternalReference(this.reflector, reference);
|
||||
if (token.identifier) {
|
||||
result.push(token.identifier.reference);
|
||||
}
|
||||
|
@ -332,28 +333,49 @@ export class AotCompiler {
|
|||
return messageBundle;
|
||||
}
|
||||
|
||||
emitAllPartialModules({ngModuleByPipeOrDirective, files}: NgAnalyzedModules): PartialModule[] {
|
||||
// Using reduce like this is a select many pattern (where map is a select pattern)
|
||||
return files.reduce<PartialModule[]>((r, file) => {
|
||||
r.push(...this._emitPartialModule(
|
||||
emitAllPartialModules(
|
||||
{ngModuleByPipeOrDirective, files}: NgAnalyzedModules,
|
||||
r3Files: NgAnalyzedFileWithInjectables[]): PartialModule[] {
|
||||
const contextMap = new Map<string, OutputContext>();
|
||||
|
||||
const getContext = (fileName: string): OutputContext => {
|
||||
if (!contextMap.has(fileName)) {
|
||||
contextMap.set(fileName, this._createOutputContext(fileName));
|
||||
}
|
||||
return contextMap.get(fileName) !;
|
||||
};
|
||||
|
||||
files.forEach(
|
||||
file => this._compilePartialModule(
|
||||
file.fileName, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules,
|
||||
file.injectables));
|
||||
return r;
|
||||
}, []);
|
||||
file.injectables, getContext(file.fileName)));
|
||||
r3Files.forEach(
|
||||
file => this._compileShallowModules(
|
||||
file.fileName, file.shallowModules, getContext(file.fileName)));
|
||||
|
||||
return Array.from(contextMap.values())
|
||||
.map(context => ({
|
||||
fileName: context.genFilePath,
|
||||
statements: [...context.constantPool.statements, ...context.statements],
|
||||
}));
|
||||
}
|
||||
|
||||
private _emitPartialModule(
|
||||
private _compileShallowModules(
|
||||
fileName: string, shallowModules: CompileShallowModuleMetadata[],
|
||||
context: OutputContext): void {
|
||||
shallowModules.forEach(module => compileIvyModule(context, module, this._injectableCompiler));
|
||||
}
|
||||
|
||||
private _compilePartialModule(
|
||||
fileName: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
|
||||
injectables: CompileInjectableMetadata[]): PartialModule[] {
|
||||
injectables: CompileInjectableMetadata[], context: OutputContext): void {
|
||||
const classes: o.ClassStmt[] = [];
|
||||
const errors: ParseError[] = [];
|
||||
|
||||
const context = this._createOutputContext(fileName);
|
||||
const hostBindingParser = new BindingParser(
|
||||
this._templateParser.expressionParser, DEFAULT_INTERPOLATION_CONFIG, null !, [], errors);
|
||||
|
||||
|
||||
// Process all components and directives
|
||||
directives.forEach(directiveType => {
|
||||
const directiveMetadata = this._metadataResolver.getDirectiveMetadata(directiveType);
|
||||
|
@ -366,28 +388,22 @@ export class AotCompiler {
|
|||
const {template: parsedTemplate, pipes: parsedPipes} =
|
||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
||||
compileIvyComponent(
|
||||
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector,
|
||||
context, directiveMetadata, parsedPipes, parsedTemplate, this.reflector,
|
||||
hostBindingParser, OutputMode.PartialClass);
|
||||
} else {
|
||||
compileIvyDirective(
|
||||
context, directiveMetadata, this._reflector, hostBindingParser,
|
||||
OutputMode.PartialClass);
|
||||
context, directiveMetadata, this.reflector, hostBindingParser, OutputMode.PartialClass);
|
||||
}
|
||||
});
|
||||
|
||||
pipes.forEach(pipeType => {
|
||||
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
|
||||
if (pipeMetadata) {
|
||||
compileIvyPipe(context, pipeMetadata, this._reflector, OutputMode.PartialClass);
|
||||
compileIvyPipe(context, pipeMetadata, this.reflector, OutputMode.PartialClass);
|
||||
}
|
||||
});
|
||||
|
||||
injectables.forEach(injectable => this._injectableCompiler.compile(injectable, context));
|
||||
|
||||
if (context.statements && context.statements.length > 0) {
|
||||
return [{fileName, statements: [...context.constantPool.statements, ...context.statements]}];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
emitAllPartialModules2(files: NgAnalyzedFileWithInjectables[]): PartialModule[] {
|
||||
|
@ -531,14 +547,14 @@ export class AotCompiler {
|
|||
if (this._options.locale) {
|
||||
const normalizedLocale = this._options.locale.replace(/_/g, '-');
|
||||
providers.push({
|
||||
token: createTokenForExternalReference(this._reflector, Identifiers.LOCALE_ID),
|
||||
token: createTokenForExternalReference(this.reflector, Identifiers.LOCALE_ID),
|
||||
useValue: normalizedLocale,
|
||||
});
|
||||
}
|
||||
|
||||
if (this._options.i18nFormat) {
|
||||
providers.push({
|
||||
token: createTokenForExternalReference(this._reflector, Identifiers.TRANSLATIONS_FORMAT),
|
||||
token: createTokenForExternalReference(this.reflector, Identifiers.TRANSLATIONS_FORMAT),
|
||||
useValue: this._options.i18nFormat
|
||||
});
|
||||
}
|
||||
|
@ -682,12 +698,12 @@ export class AotCompiler {
|
|||
listLazyRoutes(entryRoute?: string, analyzedModules?: NgAnalyzedModules): LazyRoute[] {
|
||||
const self = this;
|
||||
if (entryRoute) {
|
||||
const symbol = parseLazyRoute(entryRoute, this._reflector).referencedModule;
|
||||
const symbol = parseLazyRoute(entryRoute, this.reflector).referencedModule;
|
||||
return visitLazyRoute(symbol);
|
||||
} else if (analyzedModules) {
|
||||
const allLazyRoutes: LazyRoute[] = [];
|
||||
for (const ngModule of analyzedModules.ngModules) {
|
||||
const lazyRoutes = listLazyRoutes(ngModule, this._reflector);
|
||||
const lazyRoutes = listLazyRoutes(ngModule, this.reflector);
|
||||
for (const lazyRoute of lazyRoutes) {
|
||||
allLazyRoutes.push(lazyRoute);
|
||||
}
|
||||
|
@ -707,7 +723,7 @@ export class AotCompiler {
|
|||
}
|
||||
seenRoutes.add(symbol);
|
||||
const lazyRoutes = listLazyRoutes(
|
||||
self._metadataResolver.getNgModuleMetadata(symbol, true) !, self._reflector);
|
||||
self._metadataResolver.getNgModuleMetadata(symbol, true) !, self.reflector);
|
||||
for (const lazyRoute of lazyRoutes) {
|
||||
allLazyRoutes.push(lazyRoute);
|
||||
visitLazyRoute(lazyRoute.referencedModule, seenRoutes, allLazyRoutes);
|
||||
|
@ -748,6 +764,7 @@ export interface NgAnalyzedModules {
|
|||
export interface NgAnalyzedFileWithInjectables {
|
||||
fileName: string;
|
||||
injectables: CompileInjectableMetadata[];
|
||||
shallowModules: CompileShallowModuleMetadata[];
|
||||
}
|
||||
|
||||
export interface NgAnalyzedFile {
|
||||
|
@ -868,6 +885,7 @@ export function analyzeFileForInjectables(
|
|||
host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver,
|
||||
metadataResolver: CompileMetadataResolver, fileName: string): NgAnalyzedFileWithInjectables {
|
||||
const injectables: CompileInjectableMetadata[] = [];
|
||||
const shallowModules: CompileShallowModuleMetadata[] = [];
|
||||
if (staticSymbolResolver.hasDecorators(fileName)) {
|
||||
staticSymbolResolver.getSymbolsOf(fileName).forEach((symbol) => {
|
||||
const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol);
|
||||
|
@ -883,11 +901,17 @@ export function analyzeFileForInjectables(
|
|||
if (injectable) {
|
||||
injectables.push(injectable);
|
||||
}
|
||||
} else if (metadataResolver.isNgModule(symbol)) {
|
||||
isNgSymbol = true;
|
||||
const module = metadataResolver.getShallowModuleMetadata(symbol);
|
||||
if (module) {
|
||||
shallowModules.push(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return {fileName, injectables};
|
||||
return {fileName, injectables, shallowModules};
|
||||
}
|
||||
|
||||
function isValueExportingNonSourceFile(host: NgAnalyzeModulesHost, metadata: any): boolean {
|
||||
|
|
|
@ -90,7 +90,8 @@ export function createAotCompiler(
|
|||
const compiler = new AotCompiler(
|
||||
config, options, compilerHost, staticReflector, resolver, tmplParser,
|
||||
new StyleCompiler(urlResolver), viewCompiler, typeCheckCompiler,
|
||||
new NgModuleCompiler(staticReflector), new InjectableCompiler(staticReflector),
|
||||
new TypeScriptEmitter(), summaryResolver, symbolResolver);
|
||||
new NgModuleCompiler(staticReflector),
|
||||
new InjectableCompiler(staticReflector, !!options.enableIvy), new TypeScriptEmitter(),
|
||||
summaryResolver, symbolResolver);
|
||||
return {compiler, reflector: staticReflector};
|
||||
}
|
||||
|
|
|
@ -18,4 +18,5 @@ export interface AotCompilerOptions {
|
|||
fullTemplateTypeCheck?: boolean;
|
||||
allowEmptyCodegenFiles?: boolean;
|
||||
strictInjectionParameters?: boolean;
|
||||
enableIvy?: boolean;
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ function shouldIgnore(value: any): boolean {
|
|||
*/
|
||||
export class StaticReflector implements CompileReflector {
|
||||
private annotationCache = new Map<StaticSymbol, any[]>();
|
||||
private shallowAnnotationCache = new Map<StaticSymbol, any[]>();
|
||||
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
|
||||
private parameterCache = new Map<StaticSymbol, any[]>();
|
||||
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
|
||||
|
@ -106,8 +107,9 @@ export class StaticReflector implements CompileReflector {
|
|||
this.symbolResolver.getSymbolByModule(moduleUrl, name, containingFile));
|
||||
}
|
||||
|
||||
tryFindDeclaration(moduleUrl: string, name: string): StaticSymbol {
|
||||
return this.symbolResolver.ignoreErrorsFor(() => this.findDeclaration(moduleUrl, name));
|
||||
tryFindDeclaration(moduleUrl: string, name: string, containingFile?: string): StaticSymbol {
|
||||
return this.symbolResolver.ignoreErrorsFor(
|
||||
() => this.findDeclaration(moduleUrl, name, containingFile));
|
||||
}
|
||||
|
||||
findSymbolDeclaration(symbol: StaticSymbol): StaticSymbol {
|
||||
|
@ -135,7 +137,21 @@ export class StaticReflector implements CompileReflector {
|
|||
}
|
||||
|
||||
public annotations(type: StaticSymbol): any[] {
|
||||
let annotations = this.annotationCache.get(type);
|
||||
return this._annotations(
|
||||
type, (type: StaticSymbol, decorators: any) => this.simplify(type, decorators),
|
||||
this.annotationCache);
|
||||
}
|
||||
|
||||
public shallowAnnotations(type: StaticSymbol): any[] {
|
||||
return this._annotations(
|
||||
type, (type: StaticSymbol, decorators: any) => this.simplify(type, decorators, true),
|
||||
this.shallowAnnotationCache);
|
||||
}
|
||||
|
||||
private _annotations(
|
||||
type: StaticSymbol, simplify: (type: StaticSymbol, decorators: any) => any,
|
||||
annotationCache: Map<StaticSymbol, any[]>): any[] {
|
||||
let annotations = annotationCache.get(type);
|
||||
if (!annotations) {
|
||||
annotations = [];
|
||||
const classMetadata = this.getTypeMetadata(type);
|
||||
|
@ -146,7 +162,7 @@ export class StaticReflector implements CompileReflector {
|
|||
}
|
||||
let ownAnnotations: any[] = [];
|
||||
if (classMetadata['decorators']) {
|
||||
ownAnnotations = this.simplify(type, classMetadata['decorators']);
|
||||
ownAnnotations = simplify(type, classMetadata['decorators']);
|
||||
annotations.push(...ownAnnotations);
|
||||
}
|
||||
if (parentType && !this.summaryResolver.isLibraryFile(type.filePath) &&
|
||||
|
@ -169,7 +185,7 @@ export class StaticReflector implements CompileReflector {
|
|||
}
|
||||
}
|
||||
}
|
||||
this.annotationCache.set(type, annotations.filter(ann => !!ann));
|
||||
annotationCache.set(type, annotations.filter(ann => !!ann));
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
@ -414,7 +430,7 @@ export class StaticReflector implements CompileReflector {
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
public simplify(context: StaticSymbol, value: any): any {
|
||||
public simplify(context: StaticSymbol, value: any, lazy: boolean = false): any {
|
||||
const self = this;
|
||||
let scope = BindingScope.empty;
|
||||
const calling = new Map<StaticSymbol, boolean>();
|
||||
|
@ -775,7 +791,7 @@ export class StaticReflector implements CompileReflector {
|
|||
|
||||
let result: any;
|
||||
try {
|
||||
result = simplifyInContext(context, value, 0, 0);
|
||||
result = simplifyInContext(context, value, 0, lazy ? 1 : 0);
|
||||
} catch (e) {
|
||||
if (this.errorRecorder) {
|
||||
this.reportError(e, context);
|
||||
|
|
|
@ -527,6 +527,14 @@ export interface CompileNgModuleSummary extends CompileTypeSummary {
|
|||
modules: CompileTypeMetadata[];
|
||||
}
|
||||
|
||||
export class CompileShallowModuleMetadata {
|
||||
type: CompileTypeMetadata;
|
||||
|
||||
rawExports: any;
|
||||
rawImports: any;
|
||||
rawProviders: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata regarding compilation of a module.
|
||||
*/
|
||||
|
|
|
@ -15,6 +15,7 @@ import * as o from './output/output_ast';
|
|||
export abstract class CompileReflector {
|
||||
abstract parameters(typeOrFunc: /*Type*/ any): any[][];
|
||||
abstract annotations(typeOrFunc: /*Type*/ any): any[];
|
||||
abstract shallowAnnotations(typeOrFunc: /*Type*/ any): any[];
|
||||
abstract tryAnnotations(typeOrFunc: /*Type*/ any): any[];
|
||||
abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]};
|
||||
abstract hasLifecycleHook(type: any, lcProperty: string): boolean;
|
||||
|
|
|
@ -62,6 +62,7 @@ export class Identifiers {
|
|||
|
||||
};
|
||||
static inject: o.ExternalReference = {name: 'inject', moduleName: CORE};
|
||||
static INJECTOR: o.ExternalReference = {name: 'INJECTOR', moduleName: CORE};
|
||||
static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE};
|
||||
static defineInjectable: o.ExternalReference = {name: 'defineInjectable', moduleName: CORE};
|
||||
static ViewEncapsulation: o.ExternalReference = {
|
||||
|
@ -88,7 +89,6 @@ export class Identifiers {
|
|||
static inlineInterpolate: o.ExternalReference = {
|
||||
name: 'ɵinlineInterpolate',
|
||||
moduleName: CORE,
|
||||
|
||||
};
|
||||
static interpolate: o.ExternalReference = {name: 'ɵinterpolate', moduleName: CORE};
|
||||
static EMPTY_ARRAY: o.ExternalReference = {name: 'ɵEMPTY_ARRAY', moduleName: CORE};
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticSymbol} from './aot/static_symbol';
|
||||
import {CompileInjectableMetadata, CompileNgModuleMetadata, CompileProviderMetadata, identifierName} from './compile_metadata';
|
||||
import {CompileReflector} from './compile_reflector';
|
||||
import {InjectFlags, NodeFlags} from './core';
|
||||
|
@ -29,7 +30,10 @@ function mapEntry(key: string, value: o.Expression): MapEntry {
|
|||
}
|
||||
|
||||
export class InjectableCompiler {
|
||||
constructor(private reflector: CompileReflector) {}
|
||||
private tokenInjector: StaticSymbol;
|
||||
constructor(private reflector: CompileReflector, private alwaysGenerateDef: boolean) {
|
||||
this.tokenInjector = reflector.resolveExternalReference(Identifiers.Injector);
|
||||
}
|
||||
|
||||
private depsArray(deps: any[], ctx: OutputContext): o.Expression[] {
|
||||
return deps.map(dep => {
|
||||
|
@ -55,7 +59,16 @@ export class InjectableCompiler {
|
|||
}
|
||||
}
|
||||
}
|
||||
const tokenExpr = typeof token === 'string' ? o.literal(token) : ctx.importExpr(token);
|
||||
|
||||
let tokenExpr: o.Expression;
|
||||
if (typeof token === 'string') {
|
||||
tokenExpr = o.literal(token);
|
||||
} else if (token === this.tokenInjector && this.alwaysGenerateDef) {
|
||||
tokenExpr = o.importExpr(Identifiers.INJECTOR);
|
||||
} else {
|
||||
tokenExpr = ctx.importExpr(token);
|
||||
}
|
||||
|
||||
if (flags !== InjectFlags.Default || defaultValue !== undefined) {
|
||||
args = [tokenExpr, o.literal(defaultValue), o.literal(flags)];
|
||||
} else {
|
||||
|
@ -65,7 +78,7 @@ export class InjectableCompiler {
|
|||
});
|
||||
}
|
||||
|
||||
private factoryFor(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression {
|
||||
factoryFor(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression {
|
||||
let retValue: o.Expression;
|
||||
if (injectable.useExisting) {
|
||||
retValue = o.importExpr(Identifiers.inject).callFn([ctx.importExpr(injectable.useExisting)]);
|
||||
|
@ -90,8 +103,10 @@ export class InjectableCompiler {
|
|||
|
||||
injectableDef(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression {
|
||||
let providedIn: o.Expression = o.NULL_EXPR;
|
||||
if (injectable.providedIn) {
|
||||
if (typeof injectable.providedIn === 'string') {
|
||||
if (injectable.providedIn !== undefined) {
|
||||
if (injectable.providedIn === null) {
|
||||
providedIn = o.NULL_EXPR;
|
||||
} else if (typeof injectable.providedIn === 'string') {
|
||||
providedIn = o.literal(injectable.providedIn);
|
||||
} else {
|
||||
providedIn = ctx.importExpr(injectable.providedIn);
|
||||
|
@ -106,7 +121,7 @@ export class InjectableCompiler {
|
|||
}
|
||||
|
||||
compile(injectable: CompileInjectableMetadata, ctx: OutputContext): void {
|
||||
if (injectable.providedIn) {
|
||||
if (this.alwaysGenerateDef || injectable.providedIn !== undefined) {
|
||||
const className = identifierName(injectable.type) !;
|
||||
const clazz = new o.ClassStmt(
|
||||
className, null,
|
||||
|
|
|
@ -12,9 +12,9 @@ import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
|
|||
import * as cpl from './compile_metadata';
|
||||
import {CompileReflector} from './compile_reflector';
|
||||
import {CompilerConfig} from './config';
|
||||
import {ChangeDetectionStrategy, Component, Directive, Injectable, ModuleWithProviders, Provider, Query, SchemaMetadata, Type, ViewEncapsulation, createAttribute, createComponent, createHost, createInject, createInjectable, createInjectionToken, createOptional, createSelf, createSkipSelf} from './core';
|
||||
import {ChangeDetectionStrategy, Component, Directive, Injectable, ModuleWithProviders, Provider, Query, SchemaMetadata, Type, ViewEncapsulation, createAttribute, createComponent, createHost, createInject, createInjectable, createInjectionToken, createNgModule, createOptional, createSelf, createSkipSelf} from './core';
|
||||
import {DirectiveNormalizer} from './directive_normalizer';
|
||||
import {DirectiveResolver} from './directive_resolver';
|
||||
import {DirectiveResolver, findLast} from './directive_resolver';
|
||||
import {Identifiers} from './identifiers';
|
||||
import {getAllLifecycleHooks} from './lifecycle_reflector';
|
||||
import {HtmlParser} from './ml_parser/html_parser';
|
||||
|
@ -44,6 +44,7 @@ export class CompileMetadataResolver {
|
|||
private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>();
|
||||
private _ngModuleCache = new Map<Type, cpl.CompileNgModuleMetadata>();
|
||||
private _ngModuleOfTypes = new Map<Type, Type>();
|
||||
private _shallowModuleCache = new Map<Type, cpl.CompileShallowModuleMetadata>();
|
||||
|
||||
constructor(
|
||||
private _config: CompilerConfig, private _htmlParser: HtmlParser,
|
||||
|
@ -477,6 +478,26 @@ export class CompileMetadataResolver {
|
|||
return Promise.all(loading);
|
||||
}
|
||||
|
||||
getShallowModuleMetadata(moduleType: any): cpl.CompileShallowModuleMetadata|null {
|
||||
let compileMeta = this._shallowModuleCache.get(moduleType);
|
||||
if (compileMeta) {
|
||||
return compileMeta;
|
||||
}
|
||||
|
||||
const ngModuleMeta =
|
||||
findLast(this._reflector.shallowAnnotations(moduleType), createNgModule.isTypeOf);
|
||||
|
||||
compileMeta = {
|
||||
type: this._getTypeMetadata(moduleType),
|
||||
rawExports: ngModuleMeta.exports,
|
||||
rawImports: ngModuleMeta.imports,
|
||||
rawProviders: ngModuleMeta.providers,
|
||||
};
|
||||
|
||||
this._shallowModuleCache.set(moduleType, compileMeta);
|
||||
return compileMeta;
|
||||
}
|
||||
|
||||
getNgModuleMetadata(
|
||||
moduleType: any, throwIfNotFound = true,
|
||||
alreadyCollecting: Set<any>|null = null): cpl.CompileNgModuleMetadata|null {
|
||||
|
|
|
@ -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],
|
||||
})));
|
||||
}
|
|
@ -102,6 +102,11 @@ export class Identifiers {
|
|||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static defineInjector: o.ExternalReference = {
|
||||
name: 'defineInjector',
|
||||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
|
||||
|
||||
static query: o.ExternalReference = {name: 'ɵQ', moduleName: CORE};
|
||||
|
|
|
@ -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 */[]));
|
||||
}
|
|
@ -13,13 +13,13 @@
|
|||
*/
|
||||
|
||||
export * from './di/metadata';
|
||||
export {defineInjectable, Injectable, InjectableDecorator, InjectableProvider, InjectableType} from './di/injectable';
|
||||
|
||||
export * from './di/defs';
|
||||
export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
|
||||
|
||||
export {inject, InjectFlags, Injector} from './di/injector';
|
||||
export {Injectable, InjectableDecorator, InjectableProvider} from './di/injectable';
|
||||
export {inject, InjectFlags, INJECTOR, Injector} from './di/injector';
|
||||
export {ReflectiveInjector} from './di/reflective_injector';
|
||||
export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider';
|
||||
export {createInjector} from './di/r3_injector';
|
||||
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
|
||||
export {ReflectiveKey} from './di/reflective_key';
|
||||
export {InjectionToken} from './di/injection_token';
|
||||
|
|
|
@ -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 || [],
|
||||
};
|
||||
}
|
|
@ -11,6 +11,7 @@ import {Type} from '../type';
|
|||
import {makeDecorator, makeParamDecorator} from '../util/decorators';
|
||||
import {getClosureSafeProperty} from '../util/property';
|
||||
|
||||
import {InjectableDef, InjectableType, defineInjectable} from './defs';
|
||||
import {inject, injectArgs} from './injector';
|
||||
import {ClassSansProvider, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, StaticClassProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './provider';
|
||||
|
||||
|
@ -108,22 +109,6 @@ export function convertInjectableProviderToFactory(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an `InjectableDef` which defines how a token will be constructed by the DI system, and
|
||||
* in which injectors (if any) it will be available.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function defineInjectable<T>(opts: {
|
||||
providedIn?: Type<any>| 'root' | null,
|
||||
factory: () => T,
|
||||
}): InjectableDef<T> {
|
||||
return {
|
||||
providedIn: opts.providedIn || null,
|
||||
factory: opts.factory,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Injectable decorator and metadata.
|
||||
*
|
||||
|
@ -132,21 +117,16 @@ export function defineInjectable<T>(opts: {
|
|||
*/
|
||||
export const Injectable: InjectableDecorator = makeDecorator(
|
||||
'Injectable', undefined, undefined, undefined,
|
||||
(injectableType: Type<any>,
|
||||
(injectableType: InjectableType<any>,
|
||||
options: {providedIn?: Type<any>| 'root' | null} & InjectableProvider) => {
|
||||
if (options && options.providedIn) {
|
||||
(injectableType as InjectableType<any>).ngInjectableDef = defineInjectable({
|
||||
if (options && options.providedIn !== undefined) {
|
||||
injectableType.ngInjectableDef = defineInjectable({
|
||||
providedIn: options.providedIn,
|
||||
factory: convertInjectableProviderToFactory(injectableType, options)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export interface InjectableDef<T> {
|
||||
providedIn: Type<any>|'root'|null;
|
||||
factory: () => T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type representing injectable service.
|
||||
*
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Type} from '../type';
|
||||
|
||||
import {InjectableDef, defineInjectable} from './injectable';
|
||||
import {InjectableDef, defineInjectable} from './defs';
|
||||
|
||||
/**
|
||||
* Creates a token that can be used in a DI Provider.
|
||||
|
@ -26,8 +26,24 @@ import {InjectableDef, defineInjectable} from './injectable';
|
|||
* // myInterface is inferred to be MyInterface.
|
||||
* ```
|
||||
*
|
||||
* When creating an `InjectionToken`, you can optionally specify a factory function which returns
|
||||
* (possibly by creating) a default value of the parameterized type `T`. This sets up the
|
||||
* `InjectionToken` using this factory as a provider as if it was defined explicitly in the
|
||||
* application's root injector. If the factory function, which takes zero arguments, needs to inject
|
||||
* dependencies, it can do so using the `inject` function. See below for an example.
|
||||
*
|
||||
* Additionally, if a `factory` is specified you can also specify the `providedIn` option, which
|
||||
* overrides the above behavior and marks the token as belonging to a particular `@NgModule`. As
|
||||
* mentioned above, `'root'` is the default value for `providedIn`.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* #### Tree-shakeable InjectionToken
|
||||
*
|
||||
* {@example core/di/ts/injector_spec.ts region='ShakeableInjectionToken'}
|
||||
*
|
||||
* #### Plain InjectionToken
|
||||
*
|
||||
* {@example core/di/ts/injector_spec.ts region='InjectionToken'}
|
||||
*
|
||||
* @stable
|
||||
|
@ -54,3 +70,7 @@ export class InjectionToken<T> {
|
|||
|
||||
toString(): string { return `InjectionToken ${this._desc}`; }
|
||||
}
|
||||
|
||||
export interface InjectableDefToken<T> extends InjectionToken<T> {
|
||||
ngInjectableDef: InjectableDef<T>;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
import {Type} from '../type';
|
||||
import {stringify} from '../util';
|
||||
|
||||
import {InjectableDef, defineInjectable} from './defs';
|
||||
import {resolveForwardRef} from './forward_ref';
|
||||
import {InjectionToken} from './injection_token';
|
||||
import {Inject, Optional, Self, SkipSelf} from './metadata';
|
||||
|
@ -17,7 +19,17 @@ export const SOURCE = '__source';
|
|||
const _THROW_IF_NOT_FOUND = new Object();
|
||||
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
|
||||
|
||||
class _NullInjector implements Injector {
|
||||
/**
|
||||
* An InjectionToken that gets the current `Injector` for `createInjector()`-style injectors.
|
||||
*
|
||||
* Requesting this token instead of `Injector` allows `StaticInjector` to be tree-shaken from a
|
||||
* project.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const INJECTOR = new InjectionToken<Injector>('INJECTOR');
|
||||
|
||||
export class NullInjector implements Injector {
|
||||
get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
|
||||
if (notFoundValue === _THROW_IF_NOT_FOUND) {
|
||||
throw new Error(`NullInjectorError: No provider for ${stringify(token)}!`);
|
||||
|
@ -48,7 +60,7 @@ class _NullInjector implements Injector {
|
|||
*/
|
||||
export abstract class Injector {
|
||||
static THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
|
||||
static NULL: Injector = new _NullInjector();
|
||||
static NULL: Injector = new NullInjector();
|
||||
|
||||
/**
|
||||
* Retrieves an instance from the injector based on the provided token.
|
||||
|
@ -87,6 +99,11 @@ export abstract class Injector {
|
|||
return new StaticInjector(options.providers, options.parent, options.name || null);
|
||||
}
|
||||
}
|
||||
|
||||
static ngInjectableDef = defineInjectable({
|
||||
providedIn: 'any' as any,
|
||||
factory: () => inject(INJECTOR),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -100,7 +117,7 @@ const MULTI_PROVIDER_FN = function(): any[] {
|
|||
return Array.prototype.slice.call(arguments);
|
||||
};
|
||||
const GET_PROPERTY_NAME = {} as any;
|
||||
const USE_VALUE =
|
||||
export const USE_VALUE =
|
||||
getClosureSafeProperty<ValueProvider>({provide: String, useValue: GET_PROPERTY_NAME});
|
||||
const NG_TOKEN_PATH = 'ngTokenPath';
|
||||
const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
|
||||
|
@ -127,6 +144,8 @@ export class StaticInjector implements Injector {
|
|||
const records = this._records = new Map<any, Record>();
|
||||
records.set(
|
||||
Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
|
||||
records.set(
|
||||
INJECTOR, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
|
||||
recursivelyProcessProviders(records, providers);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -6,10 +6,13 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Provider} from '../di';
|
||||
import {InjectorDef, InjectorType, defineInjector} from '../di/defs';
|
||||
import {convertInjectableProviderToFactory} from '../di/injectable';
|
||||
import {Provider} from '../di/provider';
|
||||
import {Type} from '../type';
|
||||
import {TypeDecorator, makeDecorator} from '../util/decorators';
|
||||
|
||||
|
||||
/**
|
||||
* A wrapper around a module that also includes the providers.
|
||||
*
|
||||
|
@ -190,5 +193,17 @@ export interface NgModule {
|
|||
* @stable
|
||||
* @Annotation
|
||||
*/
|
||||
export const NgModule: NgModuleDecorator =
|
||||
makeDecorator('NgModule', (ngModule: NgModule) => ngModule);
|
||||
export const NgModule: NgModuleDecorator = makeDecorator(
|
||||
'NgModule', (ngModule: NgModule) => ngModule, undefined, undefined,
|
||||
(moduleType: InjectorType<any>, metadata: NgModule) => {
|
||||
let imports = (metadata && metadata.imports) || [];
|
||||
if (metadata && metadata.exports) {
|
||||
imports = [...imports, metadata.exports];
|
||||
}
|
||||
|
||||
moduleType.ngInjectorDef = defineInjector({
|
||||
factory: convertInjectableProviderToFactory(moduleType, {useClass: moduleType}),
|
||||
providers: metadata && metadata.providers,
|
||||
imports: imports,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InjectableDef} from '../di/defs';
|
||||
import {resolveForwardRef} from '../di/forward_ref';
|
||||
import {InjectableDef} from '../di/injectable';
|
||||
import {InjectFlags, Injector, setCurrentInjector} from '../di/injector';
|
||||
import {INJECTOR, InjectFlags, Injector, setCurrentInjector} from '../di/injector';
|
||||
import {APP_ROOT} from '../di/scope';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {stringify} from '../util';
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
|
||||
import {InjectableType, Injector} from '../di';
|
||||
import {Injector} from '../di';
|
||||
import {InjectableType} from '../di/injectable';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {ComponentFactory} from '../linker/component_factory';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
|
|
|
@ -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",
|
||||
)
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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();
|
|
@ -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); });
|
||||
});
|
|
@ -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);
|
|
@ -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.');
|
||||
});
|
||||
});
|
|
@ -473,7 +473,7 @@ function factoryFn(a: any){}
|
|||
describe('displayName', () => {
|
||||
it('should work', () => {
|
||||
expect(Injector.create([Engine.PROVIDER, {provide: BrokenEngine, useValue: null}]).toString())
|
||||
.toEqual('StaticInjector[Injector, Engine, BrokenEngine]');
|
||||
.toEqual('StaticInjector[Injector, InjectionToken INJECTOR, Engine, BrokenEngine]');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,11 +6,9 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, ContentChild, Directive, Injectable, Injector, Input, NgModule, NgModuleFactory, NgModuleRef, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '../../../src/core';
|
||||
import {Component, ContentChild, Directive, Injectable, Injector, InjectorDef, Input, NgModule, NgModuleFactory, NgModuleRef, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, defineInjector} from '../../../src/core';
|
||||
import * as r3 from '../../../src/render3/index';
|
||||
|
||||
import {$pending_pr_22458$} from './pending_api_spec';
|
||||
|
||||
const details_elided = {
|
||||
type: Object,
|
||||
} as any;
|
||||
|
@ -60,7 +58,7 @@ export class LibBComponent {
|
|||
@NgModule({declarations: [LibAComponent], imports: []})
|
||||
export class LibBModule {
|
||||
// COMPILER GENERATED
|
||||
static ngInjectorDef = $pending_pr_22458$.defineInjector(details_elided);
|
||||
static ngInjectorDef = defineInjector(details_elided);
|
||||
}
|
||||
// END FILE: node_modules/libB/module.ts
|
||||
// BEGIN FILE: node_modules/libB/module.metadata.json
|
||||
|
@ -92,7 +90,7 @@ export class AppComponent {
|
|||
@NgModule({declarations: [LibAComponent], imports: []})
|
||||
export class AppModule {
|
||||
// COMPILER GENERATED
|
||||
static ngInjectorDef = $pending_pr_22458$.defineInjector(details_elided);
|
||||
static ngInjectorDef = defineInjector(details_elided);
|
||||
}
|
||||
// END FILE: src/app.ts
|
||||
|
||||
|
@ -113,7 +111,7 @@ function ngBackPatch_node_modules_libB_module_LibAComponent() {
|
|||
}
|
||||
|
||||
function ngBackPatch_node_modules_libB_module_LibAModule() {
|
||||
(LibAModule as any).ngInjectorDef = $pending_pr_22458$.defineInjector(details_elided);
|
||||
(LibAModule as any).ngInjectorDef = defineInjector(details_elided);
|
||||
}
|
||||
|
||||
export const AppModuleFactory: NgModuleFactory<AppModule>&{patchedDeps: boolean} = {
|
||||
|
|
|
@ -6,12 +6,10 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Inject, InjectFlags, Injectable, Injector, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, SkipSelf, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core';
|
||||
import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, INJECTOR, Inject, InjectFlags, Injectable, InjectableDef, Injector, InjectorDef, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, SkipSelf, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, defineInjectable, defineInjector} from '../../../src/core';
|
||||
import * as $r3$ from '../../../src/core_render3_private_export';
|
||||
import {renderComponent, toHtml} from '../render_util';
|
||||
|
||||
import {$pending_pr_22458$} from './pending_api_spec';
|
||||
|
||||
|
||||
|
||||
/// See: `normative.md`
|
||||
|
@ -118,7 +116,7 @@ describe('injection', () => {
|
|||
@Injectable()
|
||||
class ServiceA {
|
||||
// NORMATIVE
|
||||
static ngInjectableDef = $pending_pr_22458$.defineInjectable({
|
||||
static ngInjectableDef = defineInjectable({
|
||||
factory: function ServiceA_Factory() { return new ServiceA(); },
|
||||
});
|
||||
// /NORMATIVE
|
||||
|
@ -127,7 +125,7 @@ describe('injection', () => {
|
|||
@Injectable()
|
||||
class ServiceB {
|
||||
// NORMATIVE
|
||||
static ngInjectableDef = $pending_pr_22458$.defineInjectable({
|
||||
static ngInjectableDef = defineInjectable({
|
||||
factory: function ServiceA_Factory() { return new ServiceB(); },
|
||||
});
|
||||
// /NORMATIVE
|
||||
|
@ -146,8 +144,7 @@ describe('injection', () => {
|
|||
tag: 'my-app',
|
||||
factory: function MyApp_Factory() {
|
||||
return new MyApp(
|
||||
$r3$.ɵdirectiveInject(ServiceA), $r3$.ɵdirectiveInject(ServiceB),
|
||||
$pending_pr_22458$.injectInjector());
|
||||
$r3$.ɵdirectiveInject(ServiceA), $r3$.ɵdirectiveInject(ServiceB), inject(INJECTOR));
|
||||
},
|
||||
/** */
|
||||
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {},
|
||||
|
@ -169,10 +166,9 @@ describe('injection', () => {
|
|||
constructor(@Inject(String) name: String, injector: Injector) {}
|
||||
|
||||
// NORMATIVE
|
||||
static ngInjectableDef = $pending_pr_22458$.defineInjectable({
|
||||
static ngInjectableDef = defineInjectable({
|
||||
factory: function ServiceA_Factory() {
|
||||
return new ServiceA(
|
||||
$pending_pr_22458$.inject(String), $pending_pr_22458$.injectInjector());
|
||||
return new ServiceA(inject(String), inject(INJECTOR));
|
||||
},
|
||||
});
|
||||
// /NORMATIVE
|
||||
|
@ -182,11 +178,9 @@ describe('injection', () => {
|
|||
class ServiceB {
|
||||
constructor(serviceA: ServiceA, @SkipSelf() injector: Injector) {}
|
||||
// NORMATIVE
|
||||
static ngInjectableDef = $pending_pr_22458$.defineInjectable({
|
||||
static ngInjectableDef = defineInjectable({
|
||||
factory: function ServiceA_Factory() {
|
||||
return new ServiceB(
|
||||
$pending_pr_22458$.inject(ServiceA),
|
||||
$pending_pr_22458$.injectInjector(InjectFlags.SkipSelf));
|
||||
return new ServiceB(inject(ServiceA), inject(INJECTOR, undefined, InjectFlags.SkipSelf));
|
||||
},
|
||||
});
|
||||
// /NORMATIVE
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, ContentChild, Directive, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '../../../src/core';
|
||||
import {Component, ContentChild, Directive, Injectable, InjectableDef, InjectorDef, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, defineInjectable, defineInjector} from '../../../src/core';
|
||||
import * as r3 from '../../../src/render3/index';
|
||||
import {$pending_pr_22458$} from './pending_api_spec';
|
||||
|
||||
|
||||
/**
|
||||
* GOALS:
|
||||
|
@ -29,7 +29,7 @@ class ThirdPartyClass {
|
|||
@Injectable()
|
||||
class CompiledWithIvy {
|
||||
// NORMATIVE
|
||||
static ngInjectableDef = $pending_pr_22458$.defineInjectable(
|
||||
static ngInjectableDef = defineInjectable(
|
||||
{factory: function CompileWithIvy_Factory() { return new CompiledWithIvy(); }});
|
||||
// /NORMATIVE
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class CompiledWithIvy {
|
|||
@NgModule({providers: [ThirdPartyClass, CompiledWithIvy]})
|
||||
class CompiledWithIvyModule {
|
||||
// NORMATIVE
|
||||
static ngInjectorDef = $pending_pr_22458$.defineInjector({
|
||||
static ngInjectorDef = defineInjector({
|
||||
providers: [ThirdPartyClass, CompiledWithIvy],
|
||||
factory: function CompiledWithIvyModule_Factory() { return new CompiledWithIvyModule(); }
|
||||
});
|
||||
|
@ -72,7 +72,7 @@ function ngPatch_depsOf_CompiledWithIvyModule() {
|
|||
}
|
||||
function ngPatch_node_modules_some_library_path_public_CompileWithIvy() {
|
||||
/** @__BUILD_OPTIMIZER_COLOCATE__ */
|
||||
(ThirdPartyClass as any).ngInjectableDef = $pending_pr_22458$.defineInjectable(
|
||||
(ThirdPartyClass as any).ngInjectableDef = defineInjectable(
|
||||
{factory: function CompileWithIvy_Factory() { return new ThirdPartyClass(); }});
|
||||
}
|
||||
// /NORMATIVE
|
||||
|
|
|
@ -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; }
|
||||
}
|
|
@ -7,11 +7,10 @@
|
|||
*/
|
||||
|
||||
import {NgForOf, NgForOfContext} from '@angular/common';
|
||||
import {Component, ContentChild, Directive, EventEmitter, Injectable, Input, NgModule, OnDestroy, Optional, Output, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {Component, ContentChild, Directive, EventEmitter, Injectable, InjectableDef, InjectorDef, Input, NgModule, OnDestroy, Optional, Output, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, defineInjectable, defineInjector} from '@angular/core';
|
||||
import {withBody} from '@angular/core/testing';
|
||||
|
||||
import * as r3 from '../../../src/render3/index';
|
||||
import {$pending_pr_22458$} from './pending_api_spec';
|
||||
|
||||
/// See: `normative.md`
|
||||
|
||||
|
@ -33,7 +32,7 @@ class AppState {
|
|||
];
|
||||
|
||||
// NORMATIVE
|
||||
static ngInjectableDef = $pending_pr_22458$.defineInjectable({factory: () => new AppState()});
|
||||
static ngInjectableDef = defineInjectable({factory: () => new AppState()});
|
||||
// /NORMATIVE
|
||||
}
|
||||
|
||||
|
@ -158,7 +157,7 @@ const e1_attrs = ['type', 'checkbox'];
|
|||
})
|
||||
class ToDoAppModule {
|
||||
// NORMATIVE
|
||||
static ngInjectorDef = $pending_pr_22458$.defineInjector({
|
||||
static ngInjectorDef = defineInjector({
|
||||
factory: () => new ToDoAppModule(),
|
||||
providers: [AppState],
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {NgModuleRef} from '@angular/core';
|
||||
import {InjectableDef} from '@angular/core/src/di/injectable';
|
||||
import {InjectableDef, defineInjectable} from '@angular/core/src/di/defs';
|
||||
import {InjectFlags, Injector, inject} from '@angular/core/src/di/injector';
|
||||
import {makePropDecorator} from '@angular/core/src/util/decorators';
|
||||
import {NgModuleDefinition, NgModuleProviderDef, NodeFlags} from '@angular/core/src/view';
|
||||
|
@ -24,68 +24,68 @@ class MyChildModule {}
|
|||
class NotMyModule {}
|
||||
|
||||
class Bar {
|
||||
static ngInjectableDef: InjectableDef<Bar> = {
|
||||
static ngInjectableDef: InjectableDef<Bar> = defineInjectable({
|
||||
factory: () => new Bar(),
|
||||
providedIn: MyModule,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class Baz {
|
||||
static ngInjectableDef: InjectableDef<Baz> = {
|
||||
static ngInjectableDef: InjectableDef<Baz> = defineInjectable({
|
||||
factory: () => new Baz(),
|
||||
providedIn: NotMyModule,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class HasNormalDep {
|
||||
constructor(public foo: Foo) {}
|
||||
|
||||
static ngInjectableDef: InjectableDef<HasNormalDep> = {
|
||||
static ngInjectableDef: InjectableDef<HasNormalDep> = defineInjectable({
|
||||
factory: () => new HasNormalDep(inject(Foo)),
|
||||
providedIn: MyModule,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class HasDefinedDep {
|
||||
constructor(public bar: Bar) {}
|
||||
|
||||
static ngInjectableDef: InjectableDef<HasDefinedDep> = {
|
||||
static ngInjectableDef: InjectableDef<HasDefinedDep> = defineInjectable({
|
||||
factory: () => new HasDefinedDep(inject(Bar)),
|
||||
providedIn: MyModule,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class HasOptionalDep {
|
||||
constructor(public baz: Baz|null) {}
|
||||
|
||||
static ngInjectableDef: InjectableDef<HasOptionalDep> = {
|
||||
static ngInjectableDef: InjectableDef<HasOptionalDep> = defineInjectable({
|
||||
factory: () => new HasOptionalDep(inject(Baz, null)),
|
||||
providedIn: MyModule,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class ChildDep {
|
||||
static ngInjectableDef: InjectableDef<ChildDep> = {
|
||||
static ngInjectableDef: InjectableDef<ChildDep> = defineInjectable({
|
||||
factory: () => new ChildDep(),
|
||||
providedIn: MyChildModule,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class FromChildWithOptionalDep {
|
||||
constructor(public baz: Baz|null) {}
|
||||
static ngInjectableDef: InjectableDef<FromChildWithOptionalDep> = {
|
||||
static ngInjectableDef: InjectableDef<FromChildWithOptionalDep> = defineInjectable({
|
||||
factory: () => new FromChildWithOptionalDep(inject(Baz, null, InjectFlags.Default)),
|
||||
providedIn: MyChildModule,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class FromChildWithSkipSelfDep {
|
||||
constructor(public depFromParent: ChildDep|null, public depFromChild: Bar|null) {}
|
||||
static ngInjectableDef: InjectableDef<FromChildWithSkipSelfDep> = {
|
||||
static ngInjectableDef: InjectableDef<FromChildWithSkipSelfDep> = defineInjectable({
|
||||
factory: () => new FromChildWithSkipSelfDep(
|
||||
inject(ChildDep, null, InjectFlags.SkipSelf), inject(Bar, null, InjectFlags.Self)),
|
||||
providedIn: MyChildModule,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function makeProviders(classes: any[], modules: any[]): NgModuleDefinition {
|
||||
|
|
|
@ -37,6 +37,9 @@ export class JitReflector implements CompileReflector {
|
|||
annotations(typeOrFunc: /*Type*/ any): any[] {
|
||||
return this.reflectionCapabilities.annotations(typeOrFunc);
|
||||
}
|
||||
shallowAnnotations(typeOrFunc: /*Type*/ any): any[] {
|
||||
throw new Error('Not supported in JIT mode');
|
||||
}
|
||||
propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]} {
|
||||
return this.reflectionCapabilities.propMetadata(typeOrFunc);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
load("@build_bazel_rules_nodejs//:defs.bzl", _npm_package = "npm_package")
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", _ts_library = "ts_library", _ts_web_test = "ts_web_test")
|
||||
load("//packages/bazel:index.bzl", _ng_module = "ng_module", _ng_package = "ng_package")
|
||||
load("//packages/bazel/src:ng_module.bzl", _ivy_ng_module = "internal_ivy_ng_module")
|
||||
|
||||
DEFAULT_TSCONFIG = "//packages:tsconfig-build.json"
|
||||
|
||||
|
@ -79,3 +80,8 @@ def ts_web_test(bootstrap = [], deps = [], **kwargs):
|
|||
bootstrap = bootstrap,
|
||||
deps = local_deps,
|
||||
**kwargs)
|
||||
|
||||
def ivy_ng_module(name, tsconfig = None, **kwargs):
|
||||
if not tsconfig:
|
||||
tsconfig = DEFAULT_TSCONFIG
|
||||
_ivy_ng_module(name = name, tsconfig = tsconfig, **kwargs)
|
||||
|
|
|
@ -186,6 +186,9 @@ export interface ContentChildrenDecorator {
|
|||
}): Query;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare function createInjector(defType: any, parent?: Injector | null): Injector;
|
||||
|
||||
/** @experimental */
|
||||
export declare function createPlatform(injector: Injector): PlatformRef;
|
||||
|
||||
|
@ -263,6 +266,13 @@ export declare function defineInjectable<T>(opts: {
|
|||
factory: () => T;
|
||||
}): InjectableDef<T>;
|
||||
|
||||
/** @experimental */
|
||||
export declare function defineInjector(options: {
|
||||
factory: () => any;
|
||||
providers?: any[];
|
||||
imports?: any[];
|
||||
}): InjectorDef<any>;
|
||||
|
||||
/** @experimental */
|
||||
export declare function destroyPlatform(): void;
|
||||
|
||||
|
@ -380,6 +390,12 @@ export interface InjectableDecorator {
|
|||
} & InjectableProvider): Injectable;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export interface InjectableDef<T> {
|
||||
factory: () => T;
|
||||
providedIn: InjectorType<any> | 'root' | 'any' | null;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare type InjectableProvider = ValueSansProvider | ExistingSansProvider | StaticClassSansProvider | ConstructorSansProvider | FactorySansProvider | ClassSansProvider;
|
||||
|
||||
|
@ -418,6 +434,7 @@ export declare abstract class Injector {
|
|||
/** @deprecated */ abstract get(token: any, notFoundValue?: any): any;
|
||||
static NULL: Injector;
|
||||
static THROW_IF_NOT_FOUND: Object;
|
||||
static ngInjectableDef: InjectableDef<Injector>;
|
||||
/** @deprecated */ static create(providers: StaticProvider[], parent?: Injector): Injector;
|
||||
static create(options: {
|
||||
providers: StaticProvider[];
|
||||
|
@ -426,6 +443,27 @@ export declare abstract class Injector {
|
|||
}): Injector;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare const INJECTOR: InjectionToken<Injector>;
|
||||
|
||||
/** @experimental */
|
||||
export interface InjectorDef<T> {
|
||||
factory: () => T;
|
||||
imports: (InjectorType<any> | InjectorTypeWithProviders<any>)[];
|
||||
providers: (Type<any> | ValueProvider | ExistingProvider | FactoryProvider | ConstructorProvider | StaticClassProvider | ClassProvider | any[])[];
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export interface InjectorType<T> extends Type<T> {
|
||||
ngInjectorDef: InjectorDef<T>;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export interface InjectorTypeWithProviders<T> {
|
||||
ngModule: InjectorType<T>;
|
||||
providers?: (Type<any> | ValueProvider | ExistingProvider | FactoryProvider | ConstructorProvider | StaticClassProvider | ClassProvider | any[])[];
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
export declare const Input: InputDecorator;
|
||||
|
||||
|
|
Loading…
Reference in New Issue