refactor(ivy): create Injector interface; remove dependency on Ivy (#28066)

This change is a prerequasity for a later change which will turn the
'di' into its own bazel package. In order to do that we have to:
- have `Injector` type be importable by Ivy. This means that we need
  to create `Injector` as a pure type in `interface` folder which is
  already a bazel package which Ivy can depend on.
- Remove the dependency of `class Injector` on Ivy so that it can be
  compiled in isolation. We do that by using `-1` as special value for
  `__NG_ELEMENT_ID__` which tells the Ivy `NodeInjector` than
  `Injector` is being requested.

PR Close #28066
This commit is contained in:
Misko Hevery 2019-01-10 23:45:02 -08:00 committed by Andrew Kushnir
parent e082fc24b2
commit fca185e191
26 changed files with 151 additions and 97 deletions

View File

@ -253,8 +253,5 @@ export {
publishGlobalUtil as ɵpublishGlobalUtil,
publishDefaultGlobalUtils as ɵpublishDefaultGlobalUtils
} from './render3/global_utils';
export {
SWITCH_INJECTOR_FACTORY__POST_R3__ as ɵSWITCH_INJECTOR_FACTORY__POST_R3__,
} from './di/injector';
// clang-format on

View File

@ -7,20 +7,20 @@
*/
/**
* @module
* @description
* The `di` module provides dependency injection container services.
* This file should not be necessary because node resolution should just default to `./di/index`!
*
* However it does not seem to work and it breaks:
* - //packages/animations/browser/test:test_web_chromium-local
* - //packages/compiler-cli/test:extract_i18n
* - //packages/compiler-cli/test:ngc
* - //packages/compiler-cli/test:perform_watch
* - //packages/compiler-cli/test/diagnostics:check_types
* - //packages/compiler-cli/test/transformers:test
* - //packages/compiler/test:test
* - //tools/public_api_guard:core_api
*
* Remove this file once the above is solved or wait until `ngc` is deleted and then it should be
* safe to delete this file.
*/
export * from './di/metadata';
export {InjectableType, InjectorType, defineInjectable, defineInjector} from './di/interface/defs';
export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
export {Injectable, InjectableDecorator, InjectableProvider} from './di/injectable';
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/interface/provider';
export {createInjector} from './di/r3_injector';
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
export {ReflectiveKey} from './di/reflective_key';
export {InjectionToken} from './di/injection_token';
export * from './di/index';

View File

@ -0,0 +1,27 @@
/**
* @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
*/
/**
* @module
* @description
* The `di` module provides dependency injection container services.
*/
export * from './metadata';
export {InjectFlags} from './interface/injector';
export {defineInjectable, defineInjector, InjectableType, InjectorType} from './interface/defs';
export {forwardRef, resolveForwardRef, ForwardRefFn} from './forward_ref';
export {Injectable, InjectableDecorator, InjectableProvider} from './injectable';
export {INJECTOR, Injector} from './injector';
export {inject} from './injector_compatibility';
export {ReflectiveInjector} from './reflective_injector';
export {StaticProvider, ValueProvider, ConstructorSansProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './interface/provider';
export {createInjector} from './r3_injector';
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './reflective_provider';
export {ReflectiveKey} from './reflective_key';
export {InjectionToken} from './injection_token';

View File

@ -10,8 +10,7 @@ import {Type} from '../interface/type';
import {compileInjectable as render3CompileInjectable} from '../render3/jit/injectable';
import {TypeDecorator, makeDecorator} from '../util/decorators';
import {InjectableDef, InjectableType, defineInjectable, getInjectableDef} from './interface/defs';
import {ClassSansProvider, ConstructorSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueSansProvider} from './interface/provider';
import {ClassSansProvider, ConstructorSansProvider, ExistingSansProvider, FactorySansProvider, InjectableDef, InjectableType, StaticClassSansProvider, ValueSansProvider, defineInjectable, getInjectableDef} from './interface';
import {convertInjectableProviderToFactory} from './util';

View File

@ -60,13 +60,17 @@ export class InjectionToken<T> {
providedIn?: Type<any>| 'root' | null,
factory: () => T
}) {
if (options !== undefined) {
this.ngInjectableDef = undefined;
if (typeof options == 'number') {
// This is a special hack to assign __NG_ELEMENT_ID__ to this instance.
// __NG_ELEMENT_ID__ is Used by Ivy to determine bloom filter id.
// We are using it to assign `-1` which is used to identify `Injector`.
(this as any).__NG_ELEMENT_ID__ = options;
} else if (options !== undefined) {
this.ngInjectableDef = defineInjectable({
providedIn: options.providedIn || 'root',
factory: options.factory,
});
} else {
this.ngInjectableDef = undefined;
}
}

View File

@ -7,16 +7,13 @@
*/
import {Type} from '../interface/type';
import {injectInjector} from '../render3/di';
import {noop} from '../util/noop';
import {getClosureSafeProperty} from '../util/property';
import {stringify} from '../util/stringify';
import {resolveForwardRef} from './forward_ref';
import {InjectionToken} from './injection_token';
import {InjectFlags, inject} from './injector_compatibility';
import {defineInjectable} from './interface/defs';
import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './interface/provider';
import {inject} from './injector_compatibility';
import {ConstructorProvider, ExistingProvider, FactoryProvider, InjectFlags, StaticClassProvider, StaticProvider, ValueProvider, defineInjectable} from './interface';
import {Inject, Optional, Self, SkipSelf} from './metadata';
export const SOURCE = '__source';
@ -31,7 +28,10 @@ export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
*
* @publicApi
*/
export const INJECTOR = new InjectionToken<Injector>('INJECTOR');
export const INJECTOR = new InjectionToken<Injector>(
'INJECTOR',
-1 as any // `-1` is used by Ivy DI system as special value to recognize it as `Injector`.
);
export class NullInjector implements Injector {
get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
@ -109,16 +109,13 @@ export abstract class Injector {
factory: () => inject(INJECTOR),
});
/** @internal */
/** @nocollapse */
static __NG_ELEMENT_ID__: () => Injector = () => SWITCH_INJECTOR_FACTORY();
/**
* @internal
* @nocollapse
*/
static __NG_ELEMENT_ID__ = -1;
}
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<T>(value: T): T {

View File

@ -11,34 +11,10 @@ import {stringify} from '../util/stringify';
import {InjectionToken} from './injection_token';
import {Injector} from './injector';
import {InjectableDef, getInjectableDef} from './interface/defs';
import {InjectFlags, InjectableDef, getInjectableDef} from './interface';
import {Inject, Optional, Self, SkipSelf} from './metadata';
/**
* Injection flags for DI.
*
* @publicApi
*/
export enum InjectFlags {
// TODO(alxhub): make this 'const' when ngc no longer writes exports of it into ngfactory files.
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`.

View File

@ -0,0 +1,15 @@
/**
* @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
*/
/**
* This file should not be necessary because node resolution should just default to `./di/index`!
*
* However it does not seem to work and it breaks web tests.
*/
export * from './interface/index';

View File

@ -0,0 +1,11 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export * from './injector';
export * from './defs';
export * from './provider';

View File

@ -0,0 +1,31 @@
/**
* @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
*/
/**
* Injection flags for DI.
*
* @publicApi
*/
export enum InjectFlags {
// TODO(alxhub): make this 'const' when ngc no longer writes exports of it into ngfactory files.
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,
}

View File

@ -6,12 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '../interface/type';
import {ReflectionCapabilities} from '../reflection/reflection_capabilities';
import {makeDecorator, makeParamDecorator} from '../util/decorators';
import {EMPTY_ARRAY} from '../view/util';
import {ClassSansProvider, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, StaticClassProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './interface/provider';
import {makeParamDecorator} from '../util/decorators';

View File

@ -13,9 +13,8 @@ import {stringify} from '../util/stringify';
import {resolveForwardRef} from './forward_ref';
import {InjectionToken} from './injection_token';
import {INJECTOR, Injector, NullInjector, THROW_IF_NOT_FOUND, USE_VALUE} from './injector';
import {InjectFlags, inject, injectArgs, setCurrentInjector} from './injector_compatibility';
import {InjectableDef, InjectableType, InjectorType, InjectorTypeWithProviders, getInjectableDef, getInjectorDef} from './interface/defs';
import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, Provider, StaticClassProvider, StaticProvider, TypeProvider, ValueProvider} from './interface/provider';
import {inject, injectArgs, setCurrentInjector} from './injector_compatibility';
import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, InjectFlags, InjectableDef, InjectableType, InjectorType, InjectorTypeWithProviders, Provider, StaticClassProvider, StaticProvider, TypeProvider, ValueProvider, getInjectableDef, getInjectorDef} from './interface';
import {APP_ROOT} from './scope';

View File

@ -7,7 +7,7 @@
*/
import {Injector, THROW_IF_NOT_FOUND} from './injector';
import {Provider} from './interface/provider';
import {Provider} from './interface';
import {Self, SkipSelf} from './metadata';
import {cyclicDependencyError, instantiationError, noProviderError, outOfBoundsError} from './reflective_errors';
import {ReflectiveKey} from './reflective_key';

View File

@ -11,7 +11,7 @@ import {reflector} from '../reflection/reflection';
import {resolveForwardRef} from './forward_ref';
import {InjectionToken} from './injection_token';
import {ClassProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ValueProvider} from './interface/provider';
import {ClassProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ValueProvider} from './interface';
import {Inject, Optional, Self, SkipSelf} from './metadata';
import {invalidProviderError, mixingMultiProvidersWithRegularProvidersError, noAnnotationError} from './reflective_errors';
import {ReflectiveKey} from './reflective_key';

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '../interface/type';
import {InjectionToken} from './injection_token';

View File

@ -11,7 +11,7 @@ import {ReflectionCapabilities} from '../reflection/reflection_capabilities';
import {getClosureSafeProperty} from '../util/property';
import {inject, injectArgs} from './injector_compatibility';
import {ClassSansProvider, ConstructorSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './interface/provider';
import {ClassSansProvider, ConstructorSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './interface';
const USE_VALUE =
getClosureSafeProperty<ValueProvider>({provide: String, useValue: getClosureSafeProperty});

View File

@ -13,6 +13,7 @@ import {injectRenderer2 as render3InjectRenderer2} from '../render3/view_engine_
import {noop} from '../util/noop';
/**
* @deprecated Use `RendererType2` (and `Renderer2`) instead.
* @publicApi

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {InjectionToken} from '../di/injection_token';
import {InjectFlags, InjectionToken} from '../di';
import {Injector} from '../di/injector';
import {InjectFlags, injectRootLimpMode, setInjectImplementation} from '../di/injector_compatibility';
import {injectRootLimpMode, setInjectImplementation} from '../di/injector_compatibility';
import {getInjectableDef, getInjectorDef} from '../di/interface/defs';
import {Type} from '../interface/type';
import {assertDefined, assertEqual} from '../util/assert';
import {getComponentDef, getDirectiveDef, getPipeDef} from './definition';
import {NG_ELEMENT_ID} from './fields';
import {DirectiveDef} from './interfaces/definition';
@ -286,6 +286,10 @@ export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): str
* Look for the injector providing the token by walking up the node injector tree and then
* the module injector tree.
*
* This function patches `token` with `__NG_ELEMENT_ID__` which contains the id for the bloom
* filter. Negative values are reserved for special objects.
* - `-1` is reserved for injecting `Injector` (implemented by `NodeInjector`)
*
* @param tNode The Node where the search for the injector should start
* @param lView The `LView` that contains the `tNode`
* @param token The token to look for
@ -316,6 +320,10 @@ export function getOrCreateInjectable<T>(
setTNodeAndViewData(savePreviousOrParentTNode, saveLView);
}
} else if (typeof bloomHash == 'number') {
if (bloomHash === -1) {
// `-1` is a special value used to identify `Injector` types.
return new NodeInjector(tNode, lView) as any;
}
// If the token has a bloom hash, then it is a token which could be in NodeInjector.
// A reference to the previous injector TView that was found while climbing the element
@ -531,6 +539,7 @@ export function getNodeInjectable(
*
* @param token the injection token
* @returns the matching bit to check in the bloom filter or `null` if the token is not known.
* When the returned value is negative then it represents special values such as `Injector`.
*/
export function bloomHashBitOrFactory(token: Type<any>| InjectionToken<any>| string): number|
Function|undefined {
@ -539,7 +548,8 @@ export function bloomHashBitOrFactory(token: Type<any>| InjectionToken<any>| str
return token.charCodeAt(0) || 0;
}
const tokenId: number|undefined = (token as any)[NG_ELEMENT_ID];
return typeof tokenId === 'number' ? tokenId & BLOOM_MASK : tokenId;
// Negative token IDs are used for special objects such as `Injector`
return (typeof tokenId === 'number' && tokenId > 0) ? tokenId & BLOOM_MASK : tokenId;
}
export function bloomHasToken(
@ -575,11 +585,6 @@ function shouldSearchParent(flags: InjectFlags, isFirstHostTNode: boolean): bool
return !(flags & InjectFlags.Self) && !(flags & InjectFlags.Host && isFirstHostTNode);
}
export function injectInjector() {
const tNode = getPreviousOrParentTNode() as TElementNode | TContainerNode | TElementContainerNode;
return new NodeInjector(tNode, getLView());
}
export class NodeInjector implements Injector {
constructor(
private _tNode: TElementNode|TContainerNode|TElementContainerNode|null,

View File

@ -19,4 +19,5 @@ export const NG_BASE_DEF = getClosureSafeProperty({ngBaseDef: getClosureSafeProp
* the key and the directive's unique ID as the value. This allows us to map directives to their
* bloom filter bit for DI.
*/
// TODO(misko): This is wrong. The NG_ELEMENT_ID should never be minified.
export const NG_ELEMENT_ID = getClosureSafeProperty({__NG_ELEMENT_ID__: getClosureSafeProperty});

View File

@ -6,10 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {InjectFlags, InjectionToken, Injector} from '../di';
import {resolveForwardRef} from '../di/forward_ref';
import {InjectionToken} from '../di/injection_token';
import {Injector} from '../di/injector';
import {InjectFlags} from '../di/injector_compatibility';
import {Type} from '../interface/type';
import {QueryList} from '../linker';
import {Sanitizer} from '../sanitization/security';

View File

@ -7,8 +7,9 @@
*/
import {InjectionToken} from '../../di/injection_token';
import {InjectFlags} from '../../di/injector_compatibility';
import {InjectFlags} from '../../di/interface';
import {Type} from '../../interface/type';
import {TElementNode} from './node';
import {LView, TData} from './view';

View File

@ -7,8 +7,7 @@
*/
import {INJECTOR, Injector} from '../di/injector';
import {InjectFlags} from '../di/injector_compatibility';
import {StaticProvider} from '../di/interface/provider';
import {InjectFlags, StaticProvider} from '../di/interface';
import {createInjector} from '../di/r3_injector';
import {Type} from '../interface/type';
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';

View File

@ -9,7 +9,7 @@
import {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef} from '../change_detection/change_detection';
import {Injector} from '../di/injector';
import {InjectFlags} from '../di/injector_compatibility';
import {InjectFlags} from '../di/interface';
import {Type} from '../interface/type';
import {ComponentFactory, ComponentRef} from '../linker/component_factory';
import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from '../linker/component_factory_resolver';

View File

@ -6,7 +6,7 @@
"name": "CIRCULAR$1"
},
{
"name": "EMPTY_ARRAY$1"
"name": "EMPTY_ARRAY$2"
},
{
"name": "EmptyErrorImpl"
@ -33,7 +33,7 @@
"name": "NOT_YET"
},
{
"name": "NULL_INJECTOR$2"
"name": "NULL_INJECTOR$1"
},
{
"name": "NullInjector"
@ -63,7 +63,7 @@
"name": "THROW_IF_NOT_FOUND"
},
{
"name": "USE_VALUE"
"name": "USE_VALUE$2"
},
{
"name": "UnsubscriptionErrorImpl"

View File

@ -16,7 +16,7 @@ import {getLView} from '@angular/core/src/render3/state';
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, SWITCH_INJECTOR_FACTORY__POST_R3__ as R3_INJECTOR_FACTORY} from '../../src/di/injector';
import {Injector} from '../../src/di/injector';
import {Type} from '../../src/interface/type';
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';
@ -368,7 +368,6 @@ 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();
}
export class MockRendererFactory implements RendererFactory3 {

View File

@ -7,8 +7,8 @@
*/
import {NgModuleRef} from '@angular/core';
import {InjectFlags, inject} from '@angular/core/src/di';
import {INJECTOR, Injector} from '@angular/core/src/di/injector';
import {InjectFlags, inject} from '@angular/core/src/di/injector_compatibility';
import {InjectableDef, defineInjectable} from '@angular/core/src/di/interface/defs';
import {NgModuleDefinition, NgModuleProviderDef, NodeFlags} from '@angular/core/src/view';
import {moduleDef} from '@angular/core/src/view/ng_module';