feat(ivy): support injection flags at runtime (#23518)

PR Close #23518
This commit is contained in:
Kara Erickson
2018-04-23 18:24:40 -07:00
committed by Igor Minar
parent ab5bc42da0
commit db77d8dc92
4 changed files with 324 additions and 52 deletions

View File

@ -413,19 +413,19 @@ function getClosureSafeProperty<T>(objWithPropertyToExtract: T): string {
* Injection flags for DI.
*/
export const enum InjectFlags {
Default = 0,
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 = 1 << 0,
Host = 0b0001,
/** Don't descend into ancestors of the node requesting injection. */
Self = 1 << 1,
Self = 0b0010,
/** Skip the node that is requesting injection. */
SkipSelf = 1 << 2,
SkipSelf = 0b0100,
/** Inject `defaultValue` instead if token not found. */
Optional = 1 << 3,
Optional = 0b1000,
}
/**
@ -467,6 +467,7 @@ export function inject<T>(token: Type<T>| InjectionToken<T>, flags = InjectFlags
return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
injectableDef.value;
}
if (flags & InjectFlags.Optional) return null;
throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
} else {
return _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags);

View File

@ -178,7 +178,8 @@ export function diPublic(def: DirectiveDef<any>): void {
* @returns The instance found
*/
export function directiveInject<T>(token: Type<T>): T;
export function directiveInject<T>(token: Type<T>, flags?: InjectFlags): T|null;
export function directiveInject<T>(token: Type<T>, flags: InjectFlags.Optional): T|null;
export function directiveInject<T>(token: Type<T>, flags: InjectFlags): T;
export function directiveInject<T>(token: Type<T>, flags = InjectFlags.Default): T|null {
return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags);
}
@ -329,8 +330,8 @@ function getClosestComponentAncestor(node: LViewNode | LElementNode): LElementNo
* @param flags Injection flags (e.g. CheckParent)
* @returns The instance found
*/
export function getOrCreateInjectable<T>(di: LInjector, token: Type<T>, flags?: InjectFlags): T|
null {
export function getOrCreateInjectable<T>(
di: LInjector, token: Type<T>, flags: InjectFlags = InjectFlags.Default): T|null {
const bloomHash = bloomHashBit(token);
// If the token has a bloom hash, then it is a directive that is public to the injection system
@ -349,7 +350,7 @@ export function getOrCreateInjectable<T>(di: LInjector, token: Type<T>, flags?:
while (injector) {
// Get the closest potential matching injector (upwards in the injector tree) that
// *potentially* has the token.
injector = bloomFindPossibleInjector(injector, bloomHash);
injector = bloomFindPossibleInjector(injector, bloomHash, flags);
// If no injector is found, we *know* that there is no ancestor injector that contains the
// token, so we abort.
@ -360,11 +361,11 @@ export function getOrCreateInjectable<T>(di: LInjector, token: Type<T>, flags?:
// At this point, we have an injector which *may* contain the token, so we step through the
// directives associated with the injector's corresponding node to get the directive instance.
const node = injector.node;
const flags = node.tNode !.flags;
const count = flags & TNodeFlags.DirectiveCountMask;
const nodeFlags = node.tNode !.flags;
const count = nodeFlags & TNodeFlags.DirectiveCountMask;
if (count !== 0) {
const start = flags >> TNodeFlags.DirectiveStartingIndexShift;
const start = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
const end = start + count;
const defs = node.view.tView.directives !;
@ -385,15 +386,19 @@ export function getOrCreateInjectable<T>(di: LInjector, token: Type<T>, flags?:
return instance;
}
// The def wasn't found anywhere on this node, so it might be a false positive.
// Traverse up the tree and continue searching.
injector = injector.parent;
// The def wasn't found anywhere on this node, so it was a false positive.
// If flags permit, traverse up the tree and continue searching.
if (flags & InjectFlags.Self || flags & InjectFlags.Host && !sameHostView(injector)) {
injector = null;
} else {
injector = injector.parent;
}
}
}
// No directive was found for the given token.
// TODO: implement optional, check-self, and check-parent.
throw new Error('Implement');
if (flags & InjectFlags.Optional) return null;
throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
}
function searchMatchesQueuedForCreation<T>(node: LNode, token: any): T|null {
@ -443,10 +448,11 @@ function bloomHashBit(type: Type<any>): number|null {
*
* @param injector The starting node injector to check
* @param bloomBit The bit to check in each injector's bloom filter
* @param flags The injection flags for this injection site (e.g. Optional or SkipSelf)
* @returns An injector that might have the directive
*/
export function bloomFindPossibleInjector(startInjector: LInjector, bloomBit: number): LInjector|
null {
export function bloomFindPossibleInjector(
startInjector: LInjector, bloomBit: number, flags: InjectFlags): LInjector|null {
// Create a mask that targets the specific bit associated with the directive we're looking for.
// JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding
// to bit positions 0 - 31 in a 32 bit integer.
@ -454,7 +460,8 @@ export function bloomFindPossibleInjector(startInjector: LInjector, bloomBit: nu
// Traverse up the injector tree until we find a potential match or until we know there *isn't* a
// match.
let injector: LInjector|null = startInjector;
let injector: LInjector|null =
flags & InjectFlags.SkipSelf ? startInjector.parent ! : startInjector;
while (injector) {
// Our bloom filter size is 256 bits, which is eight 32-bit bloom filter buckets:
// bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc.
@ -472,6 +479,8 @@ export function bloomFindPossibleInjector(startInjector: LInjector, bloomBit: nu
// this injector is a potential match.
if ((value & mask) === mask) {
return injector;
} else if (flags & InjectFlags.Self || flags & InjectFlags.Host && !sameHostView(injector)) {
return null;
}
// If the current injector does not have the directive, check the bloom filters for the ancestor
@ -491,6 +500,16 @@ export function bloomFindPossibleInjector(startInjector: LInjector, bloomBit: nu
return null;
}
/**
* Checks whether the current injector and its parent are in the same host view.
*
* This is necessary to support @Host() decorators. If @Host() is set, we should stop searching once
* the injector and its parent view don't match because it means we'd cross the view boundary.
*/
function sameHostView(injector: LInjector): boolean {
return !!injector.parent && injector.parent.node.view === injector.node.view;
}
export class ReadFromInjectorFn<T> {
constructor(readonly read: (injector: LInjector, node: LNode, directiveIndex?: number) => T) {}
}