From 2c7386c9613160c911544c1c626a6f4afdde707d Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Tue, 23 Oct 2018 14:28:15 -0700 Subject: [PATCH] feat(ivy): support injecting the injector (#26699) PR Close #26699 --- .../src/ngtsc/annotations/src/util.ts | 12 -- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 2 +- packages/compiler/src/render3/r3_factory.ts | 26 +-- packages/core/src/core_private_export.ts | 2 +- .../core/src/core_render3_private_export.ts | 4 + packages/core/src/di.ts | 3 +- packages/core/src/di/injector.ts | 161 ++--------------- .../core/src/di/injector_compatibility.ts | 165 ++++++++++++++++++ packages/core/src/di/r3_injector.ts | 3 +- packages/core/src/di/util.ts | 2 +- packages/core/src/r3_symbols.ts | 2 +- packages/core/src/render3/component_ref.ts | 3 +- packages/core/src/render3/di.ts | 23 ++- packages/core/src/render3/discovery_utils.ts | 4 +- packages/core/src/render3/instructions.ts | 2 +- .../core/src/render3/interfaces/injector.ts | 2 +- packages/core/src/render3/jit/environment.ts | 2 +- packages/core/src/render3/jit/util.ts | 6 +- packages/core/src/view/ng_module.ts | 3 +- packages/core/src/view/refs.ts | 3 +- .../bundle.golden_symbols.json | 6 +- .../hello_world/bundle.golden_symbols.json | 4 +- .../hello_world_r2/bundle.golden_symbols.json | 7 +- .../injection/bundle.golden_symbols.json | 2 +- .../bundling/todo/bundle.golden_symbols.json | 6 +- .../todo_r2/bundle.golden_symbols.json | 9 + packages/core/test/di/r3_injector_spec.ts | 3 +- packages/core/test/render3/di_spec.ts | 47 +++++ packages/core/test/render3/ivy/jit_spec.ts | 2 +- packages/core/test/render3/render_util.ts | 3 +- packages/core/test/view/ng_module_spec.ts | 3 +- 31 files changed, 305 insertions(+), 217 deletions(-) create mode 100644 packages/core/src/di/injector_compatibility.ts diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts index 426b6ac041..b64668b560 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts @@ -64,18 +64,6 @@ export function getConstructorDependencies( ErrorCode.PARAM_MISSING_TOKEN, param.nameNode, `No suitable token for parameter ${param.name || idx} of class ${clazz.name!.text}`); } - if (ts.isIdentifier(tokenExpr)) { - const importedSymbol = reflector.getImportOfIdentifier(tokenExpr); - if (importedSymbol !== null && importedSymbol.from === '@angular/core') { - switch (importedSymbol.name) { - case 'Injector': - resolved = R3ResolvedDependencyType.Injector; - break; - default: - // Leave as a Token or Attribute. - } - } - } const token = new WrappedNodeExpr(tokenExpr); useType.push({token, optional, self, skipSelf, host, resolved}); }); diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 7f9244b1f0..ebfe2018c0 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -380,7 +380,7 @@ describe('ngtsc behavioral tests', () => { const jsContents = env.getContents('test.js'); expect(jsContents) .toContain( - `factory: function FooCmp_Factory(t) { return new (t || FooCmp)(i0.ɵinjectAttribute("test"), i0.ɵdirectiveInject(ChangeDetectorRef), i0.ɵdirectiveInject(ElementRef), i0.ɵdirectiveInject(i0.INJECTOR), i0.ɵdirectiveInject(Renderer2), i0.ɵdirectiveInject(TemplateRef), i0.ɵdirectiveInject(ViewContainerRef)); }`); + `factory: function FooCmp_Factory(t) { return new (t || FooCmp)(i0.ɵinjectAttribute("test"), i0.ɵdirectiveInject(ChangeDetectorRef), i0.ɵdirectiveInject(ElementRef), i0.ɵdirectiveInject(Injector), i0.ɵdirectiveInject(Renderer2), i0.ɵdirectiveInject(TemplateRef), i0.ɵdirectiveInject(ViewContainerRef)); }`); }); it('should generate queries for components', () => { diff --git a/packages/compiler/src/render3/r3_factory.ts b/packages/compiler/src/render3/r3_factory.ts index fe4485e743..78871f48fa 100644 --- a/packages/compiler/src/render3/r3_factory.ts +++ b/packages/compiler/src/render3/r3_factory.ts @@ -95,11 +95,6 @@ export enum R3ResolvedDependencyType { * The token expression is a string representing the attribute name. */ Attribute = 1, - - /** - * The dependency is for the `Injector` type itself. - */ - Injector = 2, } /** @@ -230,22 +225,14 @@ function compileInjectDependency( dep: R3DependencyMetadata, injectFn: o.ExternalReference): o.Expression { // Interpret the dependency according to its resolved type. switch (dep.resolved) { - case R3ResolvedDependencyType.Token: - case R3ResolvedDependencyType.Injector: { + case R3ResolvedDependencyType.Token: { // Build up the injection flags according to the metadata. const flags = InjectFlags.Default | (dep.self ? InjectFlags.Self : 0) | (dep.skipSelf ? InjectFlags.SkipSelf : 0) | (dep.host ? InjectFlags.Host : 0) | (dep.optional ? InjectFlags.Optional : 0); - // Determine the token used for injection. In almost all cases this is the given token, but - // if the dependency is resolved to the `Injector` then the special `INJECTOR` token is used - // instead. - let token: o.Expression = dep.token; - if (dep.resolved === R3ResolvedDependencyType.Injector) { - token = o.importExpr(Identifiers.INJECTOR); - } // Build up the arguments to the injectFn call. - const injectArgs = [token]; + const injectArgs = [dep.token]; // If this dependency is optional or otherwise has non-default flags, then additional // parameters describing how to inject the dependency must be passed to the inject function // that's being used. @@ -280,12 +267,9 @@ export function dependenciesFromGlobalMetadata( for (let dependency of type.diDeps) { if (dependency.token) { const tokenRef = tokenReference(dependency.token); - let resolved: R3ResolvedDependencyType = R3ResolvedDependencyType.Token; - if (tokenRef === injectorRef) { - resolved = R3ResolvedDependencyType.Injector; - } else if (dependency.isAttribute) { - resolved = R3ResolvedDependencyType.Attribute; - } + let resolved: R3ResolvedDependencyType = dependency.isAttribute ? + R3ResolvedDependencyType.Attribute : + R3ResolvedDependencyType.Token; // In the case of most dependencies, the token will be a reference to a type. Sometimes, // however, it can be a string, in the case of older Angular code or @Attribute injection. diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index 7b0c67165e..3f2b6a889d 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -14,7 +14,7 @@ export {isListLikeIterable as ɵisListLikeIterable} from './change_detection/cha export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants'; export {Console as ɵConsole} from './console'; export {InjectableDef as ɵInjectableDef, InjectorDef as ɵInjectorDef, getInjectableDef as ɵgetInjectableDef} from './di/defs'; -export {inject as ɵinject, setCurrentInjector as ɵsetCurrentInjector} from './di/injector'; +export {inject as ɵinject, setCurrentInjector as ɵsetCurrentInjector} from './di/injector_compatibility'; export {APP_ROOT as ɵAPP_ROOT} from './di/scope'; export {ivyEnabled as ɵivyEnabled} from './ivy_switch'; export {ComponentFactory as ɵComponentFactory} from './linker/component_factory'; diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 99d1c17ca7..9dcd436e01 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -226,4 +226,8 @@ export { export { publishGlobalUtil as ɵpublishGlobalUtil } from './render3/publish_global_util'; +export { + SWITCH_INJECTOR_FACTORY__POST_R3__ as ɵSWITCH_INJECTOR_FACTORY__POST_R3__, +} from './di/injector'; + // clang-format on diff --git a/packages/core/src/di.ts b/packages/core/src/di.ts index 1e6b201b84..c1f5989466 100644 --- a/packages/core/src/di.ts +++ b/packages/core/src/di.ts @@ -16,7 +16,8 @@ export * from './di/metadata'; export {InjectableType, InjectorType, defineInjectable, defineInjector} from './di/defs'; export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref'; export {Injectable, InjectableDecorator, InjectableProvider} from './di/injectable'; -export {inject, InjectFlags, INJECTOR, Injector} from './di/injector'; +export {INJECTOR, Injector} from './di/injector'; +export {inject, InjectFlags} from './di/injector_compatibility'; export {ReflectiveInjector} from './di/reflective_injector'; export {StaticProvider, ValueProvider, ConstructorSansProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider'; export {createInjector} from './di/r3_injector'; diff --git a/packages/core/src/di/injector.ts b/packages/core/src/di/injector.ts index a299632fb4..ac6127f4c0 100644 --- a/packages/core/src/di/injector.ts +++ b/packages/core/src/di/injector.ts @@ -6,13 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ +import {injectInjector} from '../render3/di'; import {Type} from '../type'; import {stringify} from '../util'; +import {noop} from '../util/noop'; import {getClosureSafeProperty} from '../util/property'; -import {InjectableDef, defineInjectable, getInjectableDef} from './defs'; +import {defineInjectable} from './defs'; import {resolveForwardRef} from './forward_ref'; import {InjectionToken} from './injection_token'; +import {InjectFlags, inject} from './injector_compatibility'; import {Inject, Optional, Self, SkipSelf} from './metadata'; import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './provider'; @@ -104,8 +107,16 @@ export abstract class Injector { providedIn: 'any' as any, factory: () => inject(INJECTOR), }); + + /** @internal */ + static __NG_ELEMENT_ID__: () => Injector = () => SWITCH_INJECTOR_FACTORY(); } +export const SWITCH_INJECTOR_FACTORY__POST_R3__ = function() { + return injectInjector(); +}; +const SWITCH_INJECTOR_FACTORY__PRE_R3__ = noop; +const SWITCH_INJECTOR_FACTORY: typeof injectInjector = SWITCH_INJECTOR_FACTORY__PRE_R3__; const IDENT = function(value: T): T { @@ -336,7 +347,6 @@ function resolveToken( return value; } - function computeDeps(provider: StaticProvider): DependencyRecord[] { let deps: DependencyRecord[] = EMPTY; const providerDeps: any[] = @@ -396,150 +406,3 @@ function formatError(text: string, obj: any, source: string | null = null): stri function staticError(text: string, obj: any): Error { return new Error(formatError(text, obj)); } - -/** - * Injection flags for DI. - * - * @publicApi - */ -export const enum InjectFlags { - 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 descend into 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, -} - -/** - * Current injector value used by `inject`. - * - `undefined`: it is an error to call `inject` - * - `null`: `inject` can be called but there is no injector (limp-mode). - * - Injector instance: Use the injector for resolution. - */ -let _currentInjector: Injector|undefined|null = undefined; - -export function setCurrentInjector(injector: Injector | null | undefined): Injector|undefined|null { - const former = _currentInjector; - _currentInjector = injector; - return former; -} -/** -* Current implementation of inject. -* -* By default, it is `injectInjectorOnly`, which makes it `Injector`-only aware. It can be changed -* to `directiveInject`, which brings in the `NodeInjector` system of ivy. It is designed this -* way for two reasons: -* 1. `Injector` should not depend on ivy logic. -* 2. To maintain tree shake-ability we don't want to bring in unnecessary code. -*/ -let _injectImplementation: ((token: Type| InjectionToken, flags: InjectFlags) => T | null)| - undefined; - -/** - * Injects a token from the currently active injector. - * - * This function must be used in the context of a factory function such as one defined for an - * `InjectionToken`, and will throw an error if not called from such a context. - * - * @usageNotes - * ### Example - * - * {@example core/di/ts/injector_spec.ts region='ShakeableInjectionToken'} - * - * Within such a factory function `inject` is utilized to request injection of a dependency, instead - * of providing an additional array of dependencies as was common to do with `useFactory` providers. - * `inject` is faster and more type-safe. - * - * @publicApi - */ -export function inject(token: Type| InjectionToken): T; -export function inject(token: Type| InjectionToken, flags?: InjectFlags): T|null; -export function inject(token: Type| InjectionToken, flags = InjectFlags.Default): T|null { - return (_injectImplementation || injectInjectorOnly)(token, flags); -} - -/** - * Sets the current inject implementation. - */ -export function setInjectImplementation( - impl: ((token: Type| InjectionToken, flags?: InjectFlags) => T | null) | undefined): - ((token: Type| InjectionToken, flags?: InjectFlags) => T | null)|undefined { - const previous = _injectImplementation; - _injectImplementation = impl; - return previous; -} - -export function injectInjectorOnly(token: Type| InjectionToken): T; -export function injectInjectorOnly(token: Type| InjectionToken, flags?: InjectFlags): T| - null; -export function injectInjectorOnly( - token: Type| InjectionToken, flags = InjectFlags.Default): T|null { - if (_currentInjector === undefined) { - throw new Error(`inject() must be called from an injection context`); - } else if (_currentInjector === null) { - return injectRootLimpMode(token, undefined, flags); - } else { - return _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags); - } -} - -/** - * Injects `root` tokens in limp mode. - * - * If no injector exists, we can still inject tree-shakable providers which have `providedIn` set to - * `"root"`. This is known as the limp mode injection. In such case the value is stored in the - * `InjectableDef`. - */ -export function injectRootLimpMode( - token: Type| InjectionToken, notFoundValue: T | undefined, flags: InjectFlags): T|null { - const injectableDef: InjectableDef|null = getInjectableDef(token); - if (injectableDef && injectableDef.providedIn == 'root') { - return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() : - injectableDef.value; - } - if (flags & InjectFlags.Optional) return null; - if (notFoundValue !== undefined) return notFoundValue; - throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`); -} - -export function injectArgs(types: (Type| InjectionToken| any[])[]): any[] { - const args: any[] = []; - for (let i = 0; i < types.length; i++) { - const arg = types[i]; - if (Array.isArray(arg)) { - if (arg.length === 0) { - throw new Error('Arguments array must have arguments.'); - } - let type: Type|undefined = undefined; - let flags: InjectFlags = InjectFlags.Default; - - for (let j = 0; j < arg.length; j++) { - const meta = arg[j]; - if (meta instanceof Optional || meta.ngMetadataName === 'Optional') { - flags |= InjectFlags.Optional; - } else if (meta instanceof SkipSelf || meta.ngMetadataName === 'SkipSelf') { - flags |= InjectFlags.SkipSelf; - } else if (meta instanceof Self || meta.ngMetadataName === 'Self') { - flags |= InjectFlags.Self; - } else if (meta instanceof Inject) { - type = meta.token; - } else { - type = meta; - } - } - - args.push(inject(type !, flags)); - } else { - args.push(inject(arg)); - } - } - return args; -} diff --git a/packages/core/src/di/injector_compatibility.ts b/packages/core/src/di/injector_compatibility.ts new file mode 100644 index 0000000000..78a30ae32f --- /dev/null +++ b/packages/core/src/di/injector_compatibility.ts @@ -0,0 +1,165 @@ +/** + * @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 {stringify} from '../util'; + +import {InjectableDef, getInjectableDef} from './defs'; +import {InjectionToken} from './injection_token'; +import {Injector} from './injector'; +import {Inject, Optional, Self, SkipSelf} from './metadata'; + +/** + * Injection flags for DI. + * + * @publicApi + */ +export const enum InjectFlags { + 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 descend into 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, +} + + + +/** + * Current injector value used by `inject`. + * - `undefined`: it is an error to call `inject` + * - `null`: `inject` can be called but there is no injector (limp-mode). + * - Injector instance: Use the injector for resolution. + */ +let _currentInjector: Injector|undefined|null = undefined; + +export function setCurrentInjector(injector: Injector | null | undefined): Injector|undefined|null { + const former = _currentInjector; + _currentInjector = injector; + return former; +} + +/** + * Current implementation of inject. + * + * By default, it is `injectInjectorOnly`, which makes it `Injector`-only aware. It can be changed + * to `directiveInject`, which brings in the `NodeInjector` system of ivy. It is designed this + * way for two reasons: + * 1. `Injector` should not depend on ivy logic. + * 2. To maintain tree shake-ability we don't want to bring in unnecessary code. + */ +let _injectImplementation: ((token: Type| InjectionToken, flags: InjectFlags) => T | null)| + undefined; + +/** + * Sets the current inject implementation. + */ +export function setInjectImplementation( + impl: ((token: Type| InjectionToken, flags?: InjectFlags) => T | null) | undefined): + ((token: Type| InjectionToken, flags?: InjectFlags) => T | null)|undefined { + const previous = _injectImplementation; + _injectImplementation = impl; + return previous; +} + +export function injectInjectorOnly(token: Type| InjectionToken): T; +export function injectInjectorOnly(token: Type| InjectionToken, flags?: InjectFlags): T| + null; +export function injectInjectorOnly( + token: Type| InjectionToken, flags = InjectFlags.Default): T|null { + if (_currentInjector === undefined) { + throw new Error(`inject() must be called from an injection context`); + } else if (_currentInjector === null) { + return injectRootLimpMode(token, undefined, flags); + } else { + return _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags); + } +} + +/** + * Injects a token from the currently active injector. + * + * This function must be used in the context of a factory function such as one defined for an + * `InjectionToken`, and will throw an error if not called from such a context. + * + * @usageNotes + * ### Example + * + * {@example core/di/ts/injector_spec.ts region='ShakeableInjectionToken'} + * + * Within such a factory function `inject` is utilized to request injection of a dependency, instead + * of providing an additional array of dependencies as was common to do with `useFactory` providers. + * `inject` is faster and more type-safe. + * + * @publicApi + */ +export function inject(token: Type| InjectionToken): T; +export function inject(token: Type| InjectionToken, flags?: InjectFlags): T|null; +export function inject(token: Type| InjectionToken, flags = InjectFlags.Default): T|null { + return (_injectImplementation || injectInjectorOnly)(token, flags); +} + +/** + * Injects `root` tokens in limp mode. + * + * If no injector exists, we can still inject tree-shakable providers which have `providedIn` set to + * `"root"`. This is known as the limp mode injection. In such case the value is stored in the + * `InjectableDef`. + */ +export function injectRootLimpMode( + token: Type| InjectionToken, notFoundValue: T | undefined, flags: InjectFlags): T|null { + const injectableDef: InjectableDef|null = getInjectableDef(token); + if (injectableDef && injectableDef.providedIn == 'root') { + return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() : + injectableDef.value; + } + if (flags & InjectFlags.Optional) return null; + if (notFoundValue !== undefined) return notFoundValue; + throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`); +} + +export function injectArgs(types: (Type| InjectionToken| any[])[]): any[] { + const args: any[] = []; + for (let i = 0; i < types.length; i++) { + const arg = types[i]; + if (Array.isArray(arg)) { + if (arg.length === 0) { + throw new Error('Arguments array must have arguments.'); + } + let type: Type|undefined = undefined; + let flags: InjectFlags = InjectFlags.Default; + + for (let j = 0; j < arg.length; j++) { + const meta = arg[j]; + if (meta instanceof Optional || meta.ngMetadataName === 'Optional') { + flags |= InjectFlags.Optional; + } else if (meta instanceof SkipSelf || meta.ngMetadataName === 'SkipSelf') { + flags |= InjectFlags.SkipSelf; + } else if (meta instanceof Self || meta.ngMetadataName === 'Self') { + flags |= InjectFlags.Self; + } else if (meta instanceof Inject) { + type = meta.token; + } else { + type = meta; + } + } + + args.push(inject(type !, flags)); + } else { + args.push(inject(arg)); + } + } + return args; +} diff --git a/packages/core/src/di/r3_injector.ts b/packages/core/src/di/r3_injector.ts index 0441bb811a..a0c17d6010 100644 --- a/packages/core/src/di/r3_injector.ts +++ b/packages/core/src/di/r3_injector.ts @@ -13,7 +13,8 @@ import {stringify} from '../util'; import {InjectableDef, InjectableType, InjectorType, InjectorTypeWithProviders, getInjectableDef, getInjectorDef} from './defs'; import {resolveForwardRef} from './forward_ref'; import {InjectionToken} from './injection_token'; -import {INJECTOR, InjectFlags, Injector, NullInjector, THROW_IF_NOT_FOUND, USE_VALUE, inject, injectArgs, setCurrentInjector} from './injector'; +import {INJECTOR, Injector, NullInjector, THROW_IF_NOT_FOUND, USE_VALUE} from './injector'; +import {InjectFlags, inject, injectArgs, setCurrentInjector} from './injector_compatibility'; import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, Provider, StaticClassProvider, StaticProvider, TypeProvider, ValueProvider} from './provider'; import {APP_ROOT} from './scope'; diff --git a/packages/core/src/di/util.ts b/packages/core/src/di/util.ts index 7126cc6514..e8828521d7 100644 --- a/packages/core/src/di/util.ts +++ b/packages/core/src/di/util.ts @@ -10,7 +10,7 @@ import {ReflectionCapabilities} from '../reflection/reflection_capabilities'; import {Type} from '../type'; import {getClosureSafeProperty} from '../util/property'; -import {inject, injectArgs} from './injector'; +import {inject, injectArgs} from './injector_compatibility'; import {ClassSansProvider, ConstructorSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './provider'; const USE_VALUE = diff --git a/packages/core/src/r3_symbols.ts b/packages/core/src/r3_symbols.ts index 2df5d789bd..419683d2ff 100644 --- a/packages/core/src/r3_symbols.ts +++ b/packages/core/src/r3_symbols.ts @@ -20,7 +20,7 @@ */ export {InjectableDef as ɵInjectableDef, InjectorDef as ɵInjectorDef, defineInjectable, defineInjector} from './di/defs'; -export {inject} from './di/injector'; +export {inject} from './di/injector_compatibility'; export {NgModuleDef as ɵNgModuleDef, NgModuleDefWithMeta as ɵNgModuleDefWithMeta} from './metadata/ng_module'; export {defineNgModule as ɵdefineNgModule} from './render3/definition'; export {NgModuleFactory as ɵNgModuleFactory} from './render3/ng_module_ref'; diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index 39b009f56c..ead3d598a9 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -8,7 +8,8 @@ import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref'; import {InjectionToken} from '../di/injection_token'; -import {Injector, inject} from '../di/injector'; +import {Injector} from '../di/injector'; +import {inject} from '../di/injector_compatibility'; import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory'; import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver'; import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref'; diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 37ec98f3b2..99de608b01 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -8,7 +8,8 @@ import {getInjectableDef, getInjectorDef} from '../di/defs'; import {InjectionToken} from '../di/injection_token'; -import {InjectFlags, Injector, NullInjector, injectRootLimpMode, setInjectImplementation} from '../di/injector'; +import {Injector} from '../di/injector'; +import {InjectFlags, injectRootLimpMode, setInjectImplementation} from '../di/injector_compatibility'; import {Type} from '../type'; import {assertDefined, assertEqual} from './assert'; @@ -506,6 +507,26 @@ function shouldSearchParent(flags: InjectFlags, parentLocation: RelativeInjector (flags & InjectFlags.Host && getParentInjectorViewOffset(parentLocation) > 0)); } +export function injectInjector() { + const tNode = getPreviousOrParentTNode() as TElementNode | TContainerNode | TElementContainerNode; + return new NodeInjector(tNode, getViewData()); +} + +export class NodeInjector implements Injector { + private _injectorIndex: number; + + constructor( + private _tNode: TElementNode|TContainerNode|TElementContainerNode, + private _hostView: LViewData) { + this._injectorIndex = getOrCreateNodeInjectorForNode(_tNode, _hostView); + } + + get(token: any): any { + setTNodeAndViewData(this._tNode, this._hostView); + return getOrCreateInjectable(this._tNode, this._hostView, token); + } +} + export function getFactoryOf(type: Type): ((type: Type| null) => T)|null { const typeAny = type as any; const def = getComponentDef(typeAny) || getDirectiveDef(typeAny) || diff --git a/packages/core/src/render3/discovery_utils.ts b/packages/core/src/render3/discovery_utils.ts index e3c84a51ff..52d3e168e1 100644 --- a/packages/core/src/render3/discovery_utils.ts +++ b/packages/core/src/render3/discovery_utils.ts @@ -38,14 +38,14 @@ export function getComponent(target: {}): T|null { const context = loadContext(target) !; if (context.component === undefined) { - let lViewData = context.lViewData; + let lViewData: LViewData|null = context.lViewData; while (lViewData) { const ctx = lViewData ![CONTEXT] !as{}; if (ctx && isComponentInstance(ctx)) { context.component = ctx; break; } - lViewData = lViewData ![PARENT] !; + lViewData = lViewData[FLAGS] & LViewFlags.IsRoot ? null : lViewData ![PARENT] !; } if (context.component === undefined) { context.component = null; diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 84a8170cdc..09aa7b0f35 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -9,7 +9,7 @@ import './ng_dev_mode'; import {InjectionToken} from '../di/injection_token'; -import {InjectFlags} from '../di/injector'; +import {InjectFlags} from '../di/injector_compatibility'; import {QueryList} from '../linker'; import {Sanitizer} from '../sanitization/security'; import {StyleSanitizeFn} from '../sanitization/style_sanitizer'; diff --git a/packages/core/src/render3/interfaces/injector.ts b/packages/core/src/render3/interfaces/injector.ts index e25b1728b8..0bf4301dad 100644 --- a/packages/core/src/render3/interfaces/injector.ts +++ b/packages/core/src/render3/interfaces/injector.ts @@ -7,7 +7,7 @@ */ import {InjectionToken} from '../../di/injection_token'; -import {InjectFlags} from '../../di/injector'; +import {InjectFlags} from '../../di/injector_compatibility'; import {Type} from '../../type'; import {TElementNode} from './node'; import {LViewData, TData} from './view'; diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 12201d2c6e..ec6ff92af4 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -7,7 +7,7 @@ */ import {defineInjectable, defineInjector,} from '../../di/defs'; -import {inject} from '../../di/injector'; +import {inject} from '../../di/injector_compatibility'; import * as r3 from '../index'; import * as sanitization from '../../sanitization/sanitization'; diff --git a/packages/core/src/render3/jit/util.ts b/packages/core/src/render3/jit/util.ts index 7cbde9c6cd..74a4840c2c 100644 --- a/packages/core/src/render3/jit/util.ts +++ b/packages/core/src/render3/jit/util.ts @@ -42,11 +42,7 @@ function reflectDependency(dep: any | any[]): R3DependencyMetadata { }; function setTokenAndResolvedType(token: any): void { - if (token === Injector) { - meta.resolved = R3ResolvedDependencyType.Injector; - } else { - meta.resolved = R3ResolvedDependencyType.Token; - } + meta.resolved = R3ResolvedDependencyType.Token; meta.token = new WrappedNodeExpr(token); } diff --git a/packages/core/src/view/ng_module.ts b/packages/core/src/view/ng_module.ts index 1ce1187665..50c3c6022b 100644 --- a/packages/core/src/view/ng_module.ts +++ b/packages/core/src/view/ng_module.ts @@ -8,7 +8,8 @@ import {InjectableDef, getInjectableDef} from '../di/defs'; import {resolveForwardRef} from '../di/forward_ref'; -import {INJECTOR, InjectFlags, Injector, setCurrentInjector} from '../di/injector'; +import {INJECTOR, Injector} from '../di/injector'; +import {setCurrentInjector} from '../di/injector_compatibility'; import {APP_ROOT} from '../di/scope'; import {NgModuleRef} from '../linker/ng_module_factory'; import {stringify} from '../util'; diff --git a/packages/core/src/view/refs.ts b/packages/core/src/view/refs.ts index 6c86b4aca3..0203fdd98a 100644 --- a/packages/core/src/view/refs.ts +++ b/packages/core/src/view/refs.ts @@ -8,7 +8,8 @@ import {ApplicationRef} from '../application_ref'; import {ChangeDetectorRef} from '../change_detection/change_detection'; -import {InjectFlags, Injector} from '../di/injector'; +import {Injector} from '../di/injector'; +import {InjectFlags} from '../di/injector_compatibility'; import {ComponentFactory, ComponentRef} from '../linker/component_factory'; import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from '../linker/component_factory_resolver'; import {ElementRef} from '../linker/element_ref'; diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index e6cf299e6c..1ab1de1467 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -48,7 +48,7 @@ "name": "DefaultIterableDifferFactory" }, { - "name": "EMPTY$1" + "name": "EMPTY" }, { "name": "EMPTY_ARR" @@ -81,7 +81,7 @@ "name": "HOST_NODE" }, { - "name": "INJECTOR$1" + "name": "INJECTOR" }, { "name": "INJECTOR_SIZE" @@ -138,7 +138,7 @@ "name": "NgModuleRef" }, { - "name": "NodeInjector" + "name": "NodeInjector$1" }, { "name": "NodeInjectorFactory" diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index fb8c0e6998..6abb011ba3 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -24,7 +24,7 @@ "name": "DECLARATION_VIEW" }, { - "name": "EMPTY$1" + "name": "EMPTY" }, { "name": "EMPTY_ARRAY$1" @@ -48,7 +48,7 @@ "name": "HOST_NODE" }, { - "name": "INJECTOR$1" + "name": "INJECTOR" }, { "name": "INJECTOR_SIZE" diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index 58682de9cb..ded09e4300 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -570,7 +570,7 @@ "name": "DomElementSchemaRegistry" }, { - "name": "EMPTY" + "name": "EMPTY$1" }, { "name": "EMPTY_ARRAY$4" @@ -780,7 +780,7 @@ "name": "INHERITED_CLASS_WITH_CTOR" }, { - "name": "INJECTOR" + "name": "INJECTOR$1" }, { "name": "INJECTORRefTokenKey" @@ -1343,6 +1343,9 @@ { "name": "SWITCH_ELEMENT_REF_FACTORY" }, + { + "name": "SWITCH_INJECTOR_FACTORY" + }, { "name": "SWITCH_RENDERER2_FACTORY" }, diff --git a/packages/core/test/bundling/injection/bundle.golden_symbols.json b/packages/core/test/bundling/injection/bundle.golden_symbols.json index 3ccdb6f3c1..98a5b5820e 100644 --- a/packages/core/test/bundling/injection/bundle.golden_symbols.json +++ b/packages/core/test/bundling/injection/bundle.golden_symbols.json @@ -12,7 +12,7 @@ "name": "EmptyErrorImpl" }, { - "name": "INJECTOR" + "name": "INJECTOR$1" }, { "name": "Inject" diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index e94822a8a0..83bae1a82e 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -42,7 +42,7 @@ "name": "DefaultIterableDifferFactory" }, { - "name": "EMPTY$1" + "name": "EMPTY" }, { "name": "EMPTY_ARRAY$1" @@ -69,7 +69,7 @@ "name": "HOST_NODE" }, { - "name": "INJECTOR$1" + "name": "INJECTOR" }, { "name": "INJECTOR_SIZE" @@ -132,7 +132,7 @@ "name": "NgModuleRef" }, { - "name": "NodeInjector" + "name": "NodeInjector$1" }, { "name": "NodeInjectorFactory" diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 69b61b421f..a0b6afd297 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -593,6 +593,9 @@ { "name": "NodeInjector" }, + { + "name": "NodeInjector$1" + }, { "name": "NodeInjectorFactory" }, @@ -752,6 +755,9 @@ { "name": "SWITCH_ELEMENT_REF_FACTORY" }, + { + "name": "SWITCH_INJECTOR_FACTORY" + }, { "name": "SWITCH_RENDERER2_FACTORY" }, @@ -1982,6 +1988,9 @@ { "name": "injectElementRef" }, + { + "name": "injectInjector" + }, { "name": "injectInjectorOnly" }, diff --git a/packages/core/test/di/r3_injector_spec.ts b/packages/core/test/di/r3_injector_spec.ts index 5f7a59124c..b91a4376e5 100644 --- a/packages/core/test/di/r3_injector_spec.ts +++ b/packages/core/test/di/r3_injector_spec.ts @@ -8,7 +8,8 @@ import {defineInjectable, defineInjector} from '../../src/di/defs'; import {InjectionToken} from '../../src/di/injection_token'; -import {INJECTOR, Injector, inject} from '../../src/di/injector'; +import {INJECTOR, Injector} from '../../src/di/injector'; +import {inject} from '../../src/di/injector_compatibility'; import {R3Injector, createInjector} from '../../src/di/r3_injector'; describe('InjectorDef-based createInjector()', () => { diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 2c1e29733b..9d2ca97ea9 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -1049,6 +1049,53 @@ describe('di', () => { describe('Special tokens', () => { + describe('Injector', () => { + + it('should inject the injector', () => { + let injectorDir !: InjectorDir; + let otherInjectorDir !: OtherInjectorDir; + let divElement !: HTMLElement; + + class InjectorDir { + constructor(public injector: Injector) {} + + static ngDirectiveDef = defineDirective({ + type: InjectorDir, + selectors: [['', 'injectorDir', '']], + factory: () => injectorDir = new InjectorDir(directiveInject(Injector as any)) + }); + } + + class OtherInjectorDir { + constructor(public otherDir: InjectorDir, public injector: Injector) {} + + static ngDirectiveDef = defineDirective({ + type: OtherInjectorDir, + selectors: [['', 'otherInjectorDir', '']], + factory: () => otherInjectorDir = new OtherInjectorDir( + directiveInject(InjectorDir), directiveInject(Injector as any)) + }); + } + + + /**
*/ + const App = createComponent('app', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + element(0, 'div', ['injectorDir', '', 'otherInjectorDir', '']); + } + // testing only + divElement = load(0); + }, 1, 0, [InjectorDir, OtherInjectorDir]); + + const fixture = new ComponentFixture(App); + expect(injectorDir.injector.get(ElementRef).nativeElement).toBe(divElement); + expect(otherInjectorDir.injector.get(ElementRef).nativeElement).toBe(divElement); + expect(otherInjectorDir.injector.get(InjectorDir)).toBe(injectorDir); + expect(injectorDir.injector).not.toBe(otherInjectorDir.injector); + }); + + }); + describe('ElementRef', () => { it('should create directive with ElementRef dependencies', () => { diff --git a/packages/core/test/render3/ivy/jit_spec.ts b/packages/core/test/render3/ivy/jit_spec.ts index 51b1f35e9f..cdca28fe08 100644 --- a/packages/core/test/render3/ivy/jit_spec.ts +++ b/packages/core/test/render3/ivy/jit_spec.ts @@ -10,7 +10,7 @@ import 'reflect-metadata'; import {InjectorDef, defineInjectable} from '@angular/core/src/di/defs'; import {Injectable} from '@angular/core/src/di/injectable'; -import {inject, setCurrentInjector} from '@angular/core/src/di/injector'; +import {inject, setCurrentInjector} from '@angular/core/src/di/injector_compatibility'; import {ivyEnabled} from '@angular/core/src/ivy_switch'; import {Component, HostBinding, HostListener, Input, Output, Pipe} from '@angular/core/src/metadata/directives'; import {NgModule, NgModuleDef} from '@angular/core/src/metadata/ng_module'; diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index cc0dd52ac9..44d442a595 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -14,7 +14,7 @@ import {Renderer2} from '@angular/core/src/render/api'; import {stringifyElement} from '@angular/platform-browser/testing/src/browser_util'; import {SWITCH_CHANGE_DETECTOR_REF_FACTORY__POST_R3__ as R3_CHANGE_DETECTOR_REF_FACTORY} from '../../src/change_detection/change_detector_ref'; -import {Injector} from '../../src/di/injector'; +import {Injector, SWITCH_INJECTOR_FACTORY__POST_R3__ as R3_INJECTOR_FACTORY} from '../../src/di/injector'; import {SWITCH_ELEMENT_REF_FACTORY__POST_R3__ as R3_ELEMENT_REF_FACTORY} from '../../src/linker/element_ref'; import {SWITCH_TEMPLATE_REF_FACTORY__POST_R3__ as R3_TEMPLATE_REF_FACTORY} from '../../src/linker/template_ref'; import {SWITCH_VIEW_CONTAINER_REF_FACTORY__POST_R3__ as R3_VIEW_CONTAINER_REF_FACTORY} from '../../src/linker/view_container_ref'; @@ -322,4 +322,5 @@ export function enableIvyInjectableFactories() { R3_VIEW_CONTAINER_REF_FACTORY(ViewContainerRef, ElementRef); (ChangeDetectorRef as any)[NG_ELEMENT_ID] = () => R3_CHANGE_DETECTOR_REF_FACTORY(); (Renderer2 as any)[NG_ELEMENT_ID] = () => R3_RENDERER2_FACTORY(); + (Injector as any)[NG_ELEMENT_ID] = () => R3_INJECTOR_FACTORY(); } diff --git a/packages/core/test/view/ng_module_spec.ts b/packages/core/test/view/ng_module_spec.ts index 34ce8fbeb9..53f8a464f7 100644 --- a/packages/core/test/view/ng_module_spec.ts +++ b/packages/core/test/view/ng_module_spec.ts @@ -8,7 +8,8 @@ import {NgModuleRef} from '@angular/core'; import {InjectableDef, defineInjectable} from '@angular/core/src/di/defs'; -import {INJECTOR, InjectFlags, Injector, inject} from '@angular/core/src/di/injector'; +import {INJECTOR, Injector} from '@angular/core/src/di/injector'; +import {InjectFlags, inject} from '@angular/core/src/di/injector_compatibility'; import {makePropDecorator} from '@angular/core/src/util/decorators'; import {NgModuleDefinition, NgModuleProviderDef, NodeFlags} from '@angular/core/src/view'; import {moduleDef, moduleProvideDef, resolveNgModuleDep} from '@angular/core/src/view/ng_module';