diff --git a/goldens/public-api/core/core.d.ts b/goldens/public-api/core/core.d.ts index ad6dd6a01e..115a93cfbd 100644 --- a/goldens/public-api/core/core.d.ts +++ b/goldens/public-api/core/core.d.ts @@ -838,11 +838,11 @@ export declare interface RendererType2 { } export declare class ResolvedReflectiveFactory { - dependencies: ɵangular_packages_core_core_d[]; + dependencies: ɵangular_packages_core_core_e[]; factory: Function; constructor( factory: Function, - dependencies: ɵangular_packages_core_core_d[]); + dependencies: ɵangular_packages_core_core_e[]); } export declare interface ResolvedReflectiveProvider { diff --git a/packages/core/src/di/injector_compatibility.ts b/packages/core/src/di/injector_compatibility.ts index 3332f92dfe..8dc6e32116 100644 --- a/packages/core/src/di/injector_compatibility.ts +++ b/packages/core/src/di/injector_compatibility.ts @@ -16,14 +16,20 @@ import {resolveForwardRef} from './forward_ref'; import {getInjectImplementation, injectRootLimpMode} from './inject_switch'; import {InjectionToken} from './injection_token'; import {Injector} from './injector'; -import {InjectFlags} from './interface/injector'; +import {DecoratorFlags, InjectFlags, InternalInjectFlags} from './interface/injector'; import {ValueProvider} from './interface/provider'; -import {Host, Inject, Optional, Self, SkipSelf} from './metadata'; const _THROW_IF_NOT_FOUND = {}; export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND; +/* + * Name of a property (that we patch onto DI decorator), which is used as an annotation of which + * InjectFlag this decorator represents. This allows to avoid direct references to the DI decorators + * in the code, thus making them tree-shakable. + */ +const DI_DECORATOR_FLAG = '__NG_DI_FLAG__'; + export const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath'; const NG_TOKEN_PATH = 'ngTokenPath'; const NEW_LINE = /\n/gm; @@ -145,17 +151,14 @@ export function injectArgs(types: (Type|InjectionToken|any[])[]): any[ for (let j = 0; j < arg.length; j++) { const meta = arg[j]; - if (meta instanceof Optional || meta.ngMetadataName === 'Optional' || meta === Optional) { - flags |= InjectFlags.Optional; - } else if ( - meta instanceof SkipSelf || meta.ngMetadataName === 'SkipSelf' || meta === SkipSelf) { - flags |= InjectFlags.SkipSelf; - } else if (meta instanceof Self || meta.ngMetadataName === 'Self' || meta === Self) { - flags |= InjectFlags.Self; - } else if (meta instanceof Host || meta.ngMetadataName === 'Host' || meta === Host) { - flags |= InjectFlags.Host; - } else if (meta instanceof Inject || meta === Inject) { - type = meta.token; + const flag = getInjectFlag(meta); + if (typeof flag === 'number') { + // Special case when we handle @Inject decorator. + if (flag === DecoratorFlags.Inject) { + type = meta.token; + } else { + flags |= flag; + } } else { type = meta; } @@ -169,6 +172,30 @@ export function injectArgs(types: (Type|InjectionToken|any[])[]): any[ return args; } +/** + * Attaches a given InjectFlag to a given decorator using monkey-patching. + * Since DI decorators can be used in providers `deps` array (when provider is configured using + * `useFactory`) without initialization (e.g. `Host`) and as an instance (e.g. `new Host()`), we + * attach the flag to make it available both as a static property and as a field on decorator + * instance. + * + * @param decorator Provided DI decorator. + * @param flag InjectFlag that should be applied. + */ +export function attachInjectFlag(decorator: any, flag: InternalInjectFlags|DecoratorFlags): any { + decorator[DI_DECORATOR_FLAG] = flag; + decorator.prototype[DI_DECORATOR_FLAG] = flag; + return decorator; +} + +/** + * Reads monkey-patched property that contains InjectFlag attached to a decorator. + * + * @param token Token that may contain monkey-patched DI flags property. + */ +export function getInjectFlag(token: any): number|undefined { + return token[DI_DECORATOR_FLAG]; +} export function catchInjectorError( e: any, token: any, injectorErrorName: string, source: string|null): never { diff --git a/packages/core/src/di/interface/injector.ts b/packages/core/src/di/interface/injector.ts index 9092ef5deb..7d11dd5980 100644 --- a/packages/core/src/di/interface/injector.ts +++ b/packages/core/src/di/interface/injector.ts @@ -7,25 +7,67 @@ */ +/** + * Special flag indicating that a decorator is of type `Inject`. It's used to make `Inject` + * decorator tree-shakable (so we don't have to rely on the `instanceof` checks). + * Note: this flag is not included into the `InjectFlags` since it's an internal-only API. + */ +export const enum DecoratorFlags { + Inject = -1 +} + /** * Injection flags for DI. * * @publicApi */ export enum InjectFlags { - // TODO(alxhub): make this 'const' when ngc no longer writes exports of it into ngfactory files. + // TODO(alxhub): make this 'const' (and remove `InternalInjectFlags` enum) when ngc no longer + // writes exports of it into ngfactory files. /** Check self and check parent injector if needed */ Default = 0b0000, + /** * Specifies that an injector should retrieve a dependency from any injector until reaching the * host element of the current component. (Only used with Element Injector) */ Host = 0b0001, + /** Don't ascend to ancestors of the node requesting injection. */ Self = 0b0010, + /** Skip the node that is requesting injection. */ SkipSelf = 0b0100, + /** Inject `defaultValue` instead if token not found. */ Optional = 0b1000, } + +/** + * This enum is an exact copy of the `InjectFlags` enum above, but the difference is that this is a + * const enum, so actual enum values would be inlined in generated code. The `InjectFlags` enum can + * be turned into a const enum when ViewEngine is removed (see TODO at the `InjectFlags` enum + * above). The benefit of inlining is that we can use these flags at the top level without affecting + * tree-shaking (see "no-toplevel-property-access" tslint rule for more info). + * Keep this enum in sync with `InjectFlags` enum above. + */ +export const enum InternalInjectFlags { + /** Check self and check parent injector if needed */ + Default = 0b0000, + + /** + * Specifies that an injector should retrieve a dependency from any injector until reaching the + * host element of the current component. (Only used with Element Injector) + */ + Host = 0b0001, + + /** Don't ascend to ancestors of the node requesting injection. */ + Self = 0b0010, + + /** Skip the node that is requesting injection. */ + SkipSelf = 0b0100, + + /** Inject `defaultValue` instead if token not found. */ + Optional = 0b1000, +} \ No newline at end of file diff --git a/packages/core/src/di/metadata.ts b/packages/core/src/di/metadata.ts index d3e9275f03..913f3e8ce8 100644 --- a/packages/core/src/di/metadata.ts +++ b/packages/core/src/di/metadata.ts @@ -8,6 +8,9 @@ import {makeParamDecorator} from '../util/decorators'; +import {attachInjectFlag} from './injector_compatibility'; +import {DecoratorFlags, InternalInjectFlags} from './interface/injector'; + /** * Type of the Inject decorator / constructor function. @@ -54,8 +57,10 @@ export interface Inject { * @Annotation * @publicApi */ -export const Inject: InjectDecorator = makeParamDecorator('Inject', (token: any) => ({token})); - +export const Inject: InjectDecorator = attachInjectFlag( + // Disable tslint because `DecoratorFlags` is a const enum which gets inlined. + // tslint:disable-next-line: no-toplevel-property-access + makeParamDecorator('Inject', (token: any) => ({token})), DecoratorFlags.Inject); /** * Type of the Optional decorator / constructor function. @@ -97,7 +102,10 @@ export interface Optional {} * @Annotation * @publicApi */ -export const Optional: OptionalDecorator = makeParamDecorator('Optional'); +export const Optional: OptionalDecorator = + // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. + // tslint:disable-next-line: no-toplevel-property-access + attachInjectFlag(makeParamDecorator('Optional'), InternalInjectFlags.Optional); /** * Type of the Self decorator / constructor function. @@ -142,7 +150,10 @@ export interface Self {} * @Annotation * @publicApi */ -export const Self: SelfDecorator = makeParamDecorator('Self'); +export const Self: SelfDecorator = + // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. + // tslint:disable-next-line: no-toplevel-property-access + attachInjectFlag(makeParamDecorator('Self'), InternalInjectFlags.Self); /** @@ -187,7 +198,10 @@ export interface SkipSelf {} * @Annotation * @publicApi */ -export const SkipSelf: SkipSelfDecorator = makeParamDecorator('SkipSelf'); +export const SkipSelf: SkipSelfDecorator = + // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. + // tslint:disable-next-line: no-toplevel-property-access + attachInjectFlag(makeParamDecorator('SkipSelf'), InternalInjectFlags.SkipSelf); /** * Type of the `Host` decorator / constructor function. @@ -227,4 +241,7 @@ export interface Host {} * @Annotation * @publicApi */ -export const Host: HostDecorator = makeParamDecorator('Host'); +export const Host: HostDecorator = + // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. + // tslint:disable-next-line: no-toplevel-property-access + attachInjectFlag(makeParamDecorator('Host'), InternalInjectFlags.Host); \ No newline at end of file diff --git a/packages/core/test/bundling/forms/bundle.golden_symbols.json b/packages/core/test/bundling/forms/bundle.golden_symbols.json index a4d53d02c5..8ef0dcdff9 100644 --- a/packages/core/test/bundling/forms/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms/bundle.golden_symbols.json @@ -236,9 +236,6 @@ { "name": "FormsModule" }, - { - "name": "Host" - }, { "name": "INJECTOR" }, @@ -569,9 +566,6 @@ { "name": "SelectMultipleControlValueAccessor" }, - { - "name": "Self" - }, { "name": "ShadowDomRenderer" }, @@ -743,6 +737,9 @@ { "name": "applyView" }, + { + "name": "attachInjectFlag" + }, { "name": "attachPatchData" }, diff --git a/packages/core/test/bundling/injection/bundle.golden_symbols.json b/packages/core/test/bundling/injection/bundle.golden_symbols.json index 45532497ed..951bb4b9af 100644 --- a/packages/core/test/bundling/injection/bundle.golden_symbols.json +++ b/packages/core/test/bundling/injection/bundle.golden_symbols.json @@ -5,18 +5,12 @@ { "name": "EMPTY_ARRAY" }, - { - "name": "Host" - }, { "name": "INJECTOR" }, { "name": "INJECTOR_SCOPE" }, - { - "name": "Inject" - }, { "name": "InjectFlags" }, @@ -50,21 +44,12 @@ { "name": "NullInjector" }, - { - "name": "Optional" - }, { "name": "R3Injector" }, { "name": "ScopedService" }, - { - "name": "Self" - }, - { - "name": "SkipSelf" - }, { "name": "THROW_IF_NOT_FOUND" }, @@ -113,9 +98,6 @@ { "name": "isValueProvider" }, - { - "name": "makeParamDecorator" - }, { "name": "makeRecord" }, diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index fb4ba73571..02cdefc89a 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -290,9 +290,6 @@ { "name": "HashLocationStrategy" }, - { - "name": "Host" - }, { "name": "INITIAL_VALUE" }, @@ -731,9 +728,6 @@ { "name": "SecurityContext" }, - { - "name": "Self" - }, { "name": "ShadowDomRenderer" }, @@ -971,6 +965,9 @@ { "name": "applyView" }, + { + "name": "attachInjectFlag" + }, { "name": "attachPatchData" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 2062b31ee3..86bd5b20cf 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -221,6 +221,9 @@ { "name": "assertTemplate" }, + { + "name": "attachInjectFlag" + }, { "name": "attachPatchData" }, diff --git a/packages/core/test/di/inject_flags_spec.ts b/packages/core/test/di/inject_flags_spec.ts new file mode 100644 index 0000000000..b680ff8c5f --- /dev/null +++ b/packages/core/test/di/inject_flags_spec.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {InjectFlags, InternalInjectFlags} from '../../src/di/interface/injector'; + +describe('InjectFlags', () => { + it('should always match InternalInjectFlags', () => { + expect(InjectFlags.Default).toEqual(InternalInjectFlags.Default as number); + expect(InjectFlags.Host).toEqual(InternalInjectFlags.Host as number); + expect(InjectFlags.Self).toEqual(InternalInjectFlags.Self as number); + expect(InjectFlags.SkipSelf).toEqual(InternalInjectFlags.SkipSelf as number); + expect(InjectFlags.Optional).toEqual(InternalInjectFlags.Optional as number); + }); +}); \ No newline at end of file