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