diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 27654835bd..693a8bdbf3 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -6,12 +6,23 @@ * found in the LICENSE file at https://angular.io/license */ -import {ComponentFactory, ComponentRef as IComponentRef, ElementRef as IElementRef, EmbeddedViewRef as IEmbeddedViewRef, Injector, NgModuleRef as INgModuleRef, TemplateRef as ITemplateRef, Type, ViewContainerRef as IViewContainerRef, ViewRef as IViewRef} from '../core'; +// We are temporarily importing the existing viewEngine from core so we can be sure we are +// correctly implementing its interfaces for backwards compatibility. +import * as viewEngine from '../core'; import {BLOOM_SIZE, NG_ELEMENT_ID, getOrCreateNodeInjector} from './instructions'; import {LContainer, LNodeFlags, LNodeInjector} from './interfaces'; import {ComponentTemplate, DirectiveDef} from './public_interfaces'; -import {stringify} from './util'; +import {stringify, notImplemented} from './util'; +/** + * Injection flags for DI. + * + * Optional: The dependency is not required. + * + * CheckSelf: Should check the current node for the dependency. + * + * CheckParent: Should check parent nodes for the dependency. + */ export const enum InjectFlags { Optional = 1 << 0, CheckSelf = 1 << 1, @@ -19,17 +30,40 @@ export const enum InjectFlags { Default = CheckSelf | CheckParent } -function createError(text: string, token: any) { +/** + * Constructs an injection error with the given text and token. + * + * @param text The text of the error + * @param token The token associated with the error + * @returns The error that was created + */ +function createInjectionError(text: string, token: any) { return new Error(`ElementInjector: ${text} [${stringify(token)}]`); } -export function inject(token: Type, flags?: InjectFlags): T { +/** + * Searches for an instance of the given directive type up the injector tree and returns + * that instance if found. + * + * Specifically, it gets the bloom filter bit associated with the directive (see bloomHashBit), + * checks that bit against the bloom filter structure to identify an injector that might have + * the directive (see bloomFindPossibleInjector), then searches the directives on that injector + * for a match. + * + * If not found, it will propagate up to the next parent injector until the token + * is found or the top is reached. + * + * @param token The directive type to search for + * @param flags Injection flags (e.g. CheckParent) + * @returns The instance found + */ +export function inject(token: viewEngine.Type, flags?: InjectFlags): T { const di = getOrCreateNodeInjector(); const bloomHash = bloomHashBit(token); if (bloomHash === null) { const moduleInjector = di.injector; if (!moduleInjector) { - throw createError('NotFound', token); + throw createInjectionError('NotFound', token); } moduleInjector.get(token); } else { @@ -55,14 +89,45 @@ export function inject(token: Type, flags?: InjectFlags): T { } } } - throw createError('Not found', token); + throw createInjectionError('Not found', token); } -function bloomHashBit(type: Type): number|null { +/** + * Given a directive type, this function returns the bit in an injector's bloom filter + * that should be used to determine whether or not the directive is present. + * + * When the directive was added to the bloom filter, it was given a unique ID that can be + * retrieved on the class. Since there are only BLOOM_SIZE slots per bloom filter, the directive's + * ID must be modulo-ed by BLOOM_SIZE to get the correct bloom bit (directives share slots after + * BLOOM_SIZE is reached). + * + * @param type The directive type + * @returns The bloom bit to check for the directive + */ +function bloomHashBit(type: viewEngine.Type): number|null { let id: number|undefined = (type as any)[NG_ELEMENT_ID]; return typeof id === 'number' ? id % BLOOM_SIZE : null; } +/** + * Finds the closest injector that might have a certain directive. + * + * Each directive corresponds to a bit in an injector's bloom filter. Given the bloom bit to + * check and a starting injector, this function propagates up injectors until it finds an + * injector that contains a 1 for that bit in its bloom filter. A 1 indicates that the + * injector may have that directive. It only *may* have the directive because directives begin + * to share bloom filter bits after the BLOOM_SIZE is reached, and it could correspond to a + * different directive sharing the bit. + * + * Note: We can skip checking further injectors up the tree if an injector's cbf structure + * has a 0 for that bloom bit. Since cbf contains the merged value of all the parent + * injectors, a 0 in the bloom bit indicates that the parents definitely do not contain + * the directive and do not need to be checked. + * + * @param injector The starting injector to check + * @param bloomBit The bit to check in each injector's bloom filter + * @returns An injector that might have the directive + */ export function bloomFindPossibleInjector(injector: LNodeInjector, bloomBit: number): LNodeInjector| null { const mask = 1 << bloomBit; @@ -83,72 +148,89 @@ export function bloomFindPossibleInjector(injector: LNodeInjector, bloomBit: num return null; } - -export function injectElementRef(): IElementRef { +/** + * Creates an ElementRef and stores it on the injector. Or, if the ElementRef already + * exists, retrieves the existing ElementRef. + * + * @returns The ElementRef instance to use + */ +export function injectElementRef(): viewEngine.ElementRef { let di = getOrCreateNodeInjector(); return di.elementRef || (di.elementRef = new ElementRef(di.node.native)); } -class ElementRef implements IElementRef { +/** A ref to a node's native element. */ +class ElementRef implements viewEngine.ElementRef { readonly nativeElement: any; constructor(nativeElement: any) { this.nativeElement = nativeElement; } } - -export function injectTemplateRef(): ITemplateRef { +/** + * Creates a TemplateRef and stores it on the injector. Or, if the TemplateRef already + * exists, retrieves the existing TemplateRef. + * + * @returns The TemplateRef instance to use + */ +export function injectTemplateRef(): viewEngine.TemplateRef { let di = getOrCreateNodeInjector(); const data = (di.node as LContainer).data; if (data === null || data.template === null) { - throw createError('Directive not used in structural way.', null); + throw createInjectionError('Directive does not have a template.', null); } return di.templateRef || (di.templateRef = new TemplateRef(injectElementRef(), data.template)); } -class TemplateRef implements ITemplateRef { - readonly elementRef: IElementRef; +/** A ref to a particular template. */ +class TemplateRef implements viewEngine.TemplateRef { + readonly elementRef: viewEngine.ElementRef; - constructor(elementRef: IElementRef, template: ComponentTemplate) { + constructor(elementRef: viewEngine.ElementRef, template: ComponentTemplate) { this.elementRef = elementRef; } - createEmbeddedView(context: T): IEmbeddedViewRef { throw notImplemented(); } + createEmbeddedView(context: T): viewEngine.EmbeddedViewRef { throw notImplemented(); } } -export function injectViewContainerRef(): IViewContainerRef { +/** + * Creates a ViewContainerRef and stores it on the injector. Or, if the ViewContainerRef + * already exists, retrieves the existing ViewContainerRef. + * + * @returns The ViewContainerRef instance to use + */ +export function injectViewContainerRef(): viewEngine.ViewContainerRef { let di = getOrCreateNodeInjector(); return di.viewContainerRef || (di.viewContainerRef = new ViewContainerRef(di.node as LContainer)); } -class ViewContainerRef implements IViewContainerRef { - element: IElementRef; - injector: Injector; - parentInjector: Injector; +/** + * A ref to a container that enables adding and removing views from that container + * imperatively. + */ +class ViewContainerRef implements viewEngine.ViewContainerRef { + element: viewEngine.ElementRef; + injector: viewEngine.Injector; + parentInjector: viewEngine.Injector; constructor(node: LContainer) {} clear(): void { throw notImplemented(); } - get(index: number): IViewRef|null { throw notImplemented(); } + get(index: number): viewEngine.ViewRef|null { throw notImplemented(); } length: number; createEmbeddedView( - templateRef: ITemplateRef, context?: C|undefined, - index?: number|undefined): IEmbeddedViewRef { + templateRef: viewEngine.TemplateRef, context?: C|undefined, + index?: number|undefined): viewEngine.EmbeddedViewRef { throw notImplemented(); } createComponent( - componentFactory: ComponentFactory, index?: number|undefined, - injector?: Injector|undefined, projectableNodes?: any[][]|undefined, - ngModule?: INgModuleRef|undefined): IComponentRef { + componentFactory: viewEngine.ComponentFactory, index?: number|undefined, + injector?: viewEngine.Injector|undefined, projectableNodes?: any[][]|undefined, + ngModule?: viewEngine.NgModuleRef|undefined): viewEngine.ComponentRef { throw notImplemented(); } - insert(viewRef: IViewRef, index?: number|undefined): IViewRef { throw notImplemented(); } - move(viewRef: IViewRef, currentIndex: number): IViewRef { throw notImplemented(); } - indexOf(viewRef: IViewRef): number { throw notImplemented(); } + insert(viewRef: viewEngine.ViewRef, index?: number|undefined): viewEngine.ViewRef { throw notImplemented(); } + move(viewRef: viewEngine.ViewRef, currentIndex: number): viewEngine.ViewRef { throw notImplemented(); } + indexOf(viewRef: viewEngine.ViewRef): number { throw notImplemented(); } remove(index?: number|undefined): void { throw notImplemented(); } - detach(index?: number|undefined): IViewRef|null { throw notImplemented(); } -} - - -function notImplemented() { - return new Error('Method not implemented.'); + detach(index?: number|undefined): viewEngine.ViewRef|null { throw notImplemented(); } } diff --git a/packages/core/src/render3/util.ts b/packages/core/src/render3/util.ts index cbec7c492a..1ddcd6fa0e 100644 --- a/packages/core/src/render3/util.ts +++ b/packages/core/src/render3/util.ts @@ -20,4 +20,14 @@ export function stringify(value: any): string { if (typeof value == 'string') return value; if (value == null) return ''; return '' + value; -} \ No newline at end of file +} + +/** + * Function that throws a "not implemented" error so it's clear certain + * behaviors/methods aren't yet ready. + * + * @returns Not implemented error + */ +export function notImplemented(): Error { + return new Error('NotImplemented'); +}