docs(core): add more comments to di and fix formatting (#20855)

PR Close #20855
This commit is contained in:
Kara Erickson 2017-12-13 16:32:21 -08:00 committed by Igor Minar
parent 5bc869cb24
commit 1a9064ba2b
3 changed files with 126 additions and 82 deletions

View File

@ -6,11 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
/**
* The functions in this file verify that the assumptions we are making
* about state in an instruction are correct before implementing any logic.
* They are meant only to be called in dev mode as sanity checks.
*/
// The functions in this file verify that the assumptions we are making
// about state in an instruction are correct before implementing any logic.
// They are meant only to be called in dev mode as sanity checks.
/**
* Stringifies values such that strings are wrapped in explicit quotation marks and
@ -19,8 +17,8 @@
*
* e.g. `expected "3" to be 3` is easier to understand than `expected 3 to be 3`.
*
* @param {any} value - The value to be stringified
* @returns {string} the stringified value
* @param value The value to be stringified
* @returns The stringified value
*/
function stringifyValueForError(value: any): string {
return typeof value === 'string' ? `"${value}"` : '' + value;
@ -50,11 +48,11 @@ export function assertNotEqual<T>(actual: T, expected: T, name: string) {
/**
* Throws an error with a message constructed from the arguments.
*
* @param {T} actual - The actual value (e.g. 3)
* @param {T} expected - The expected value (e.g. 5)
* @param {string} name - The name of the value being checked (e.g. attrs.length)
* @param {string} operator - The comparison operator (e.g. <, >, ==)
* @param {(v: T) => string)} serializer - Function that maps a value to its display value
* @param actual The actual value (e.g. 3)
* @param expected The expected value (e.g. 5)
* @param name The name of the value being checked (e.g. attrs.length)
* @param operator The comparison operator (e.g. <, >, ==)
* @param serializer Function that maps a value to its display value
*/
export function assertThrow<T>(
actual: T, expected: T, name: string, operator: string,

View File

@ -14,20 +14,16 @@ import {LContainer, LNodeFlags, LNodeInjector} from './interfaces';
import {ComponentTemplate, DirectiveDef} from './public_interfaces';
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.
*/
/** Injection flags for DI. */
export const enum InjectFlags {
/** Dependency is not required. Null will be injected if there is no provider for the dependency. */
Optional = 1 << 0,
/** When resolving a dependency, include the node that is requesting injection. */
CheckSelf = 1 << 1,
/** When resolving a dependency, include ancestors of the node requesting injection. */
CheckParent = 1 << 2,
Default = CheckSelf | CheckParent
/** Default injection options: required, checks both self and ancestors. */
Default = CheckSelf | CheckParent,
}
/**
@ -53,6 +49,17 @@ function createInjectionError(text: string, token: any) {
* If not found, it will propagate up to the next parent injector until the token
* is found or the top is reached.
*
* Usage example (in factory function):
*
* class SomeDirective {
* constructor(directive: DirectiveA) {}
*
* static ngDirectiveDef = defineDirective({
* type: SomeDirective,
* factory: () => new SomeDirective(inject(DirectiveA))
* });
* }
*
* @param token The directive type to search for
* @param flags Injection flags (e.g. CheckParent)
* @returns The instance found
@ -60,6 +67,9 @@ function createInjectionError(text: string, token: any) {
export function inject<T>(token: viewEngine.Type<T>, flags?: InjectFlags): T {
const di = getOrCreateNodeInjector();
const bloomHash = bloomHashBit(token);
// If the token has a bloom hash, then it is a directive that is public to the injection system
// (diPublic). If there is no hash, fall back to the module injector.
if (bloomHash === null) {
const moduleInjector = di.injector;
if (!moduleInjector) {
@ -68,27 +78,49 @@ export function inject<T>(token: viewEngine.Type<T>, flags?: InjectFlags): T {
moduleInjector.get(token);
} else {
let injector: LNodeInjector|null = di;
while (injector) {
// Get the closest potential matching injector (upwards in the injector tree) that
// *potentially* has the token.
injector = bloomFindPossibleInjector(injector, bloomHash);
if (injector) {
const node = injector.node;
const flags = node.flags;
let size = flags & LNodeFlags.SIZE_MASK;
if (size !== 0) {
size = size >> LNodeFlags.SIZE_SHIFT;
const start = flags >> LNodeFlags.INDX_SHIFT;
const ngStaticData = node.view.ngStaticData;
for (let i = start, ii = start + size; i < ii; i++) {
const def = ngStaticData[i] as DirectiveDef<any>;
if (def.diPublic && def.type == token) {
return node.view.data[i];
}
// If no injector is found, we *know* that there is no ancestor injector that contains the
// token, so we abort.
if (!injector) { break; }
// 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;
// The size of the node's directive's list is stored in certain bits of the node's flags,
// so exact it with a mask and shift it back such that the bits reflect the real value.
const flags = node.flags;
const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT;
if (size !== 0) {
// The start index of the directives list is also part of the node's flags, but there is
// nothing to the "left" of it so it doesn't need a mask.
const start = flags >> LNodeFlags.INDX_SHIFT;
const ngStaticData = node.view.ngStaticData;
for (let i = start, ii = start + size; i < ii; i++) {
// Get the definition for the directive at this index and, if it is injectable (diPublic),
// and matches the given token, return the directive instance.
const directiveDef = ngStaticData[i] as DirectiveDef<any>;
if (directiveDef.diPublic && directiveDef.type == token) {
return node.view.data[i];
}
}
injector = injector.parent;
}
// If we *didn't* find the directive for the token from the candidate injector, we had a false
// positive. Traverse up the tree and continue.
injector = injector.parent;
}
}
// No directive was found for the given token.
// TODO: implement optional, check-self, and check-parent.
throw createInjectionError('Not found', token);
}
@ -113,7 +145,7 @@ function bloomHashBit(type: viewEngine.Type<any>): number|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
* check and a starting injector, this function traverses 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
@ -128,22 +160,36 @@ function bloomHashBit(type: viewEngine.Type<any>): number|null {
* @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 {
export function bloomFindPossibleInjector(startInjector: LNodeInjector, bloomBit: number): LNodeInjector|
null {
// Create a mask that targets the specific bit associated with the directive we're looking for.
// This will be a number between 0 and 31, corresponding to a bit position in a 32 bit integer.
const mask = 1 << bloomBit;
let di: LNodeInjector|null = injector;
while (di) {
// See if the current injector may have the value.
// Traverse up the injector tree until we find a potential match or until we know there *isn't* a
// match.
let injector: LNodeInjector|null = startInjector;
while (injector) {
// Our bloom filter size is 128 bits, which is four 32-bit bloom filter buckets:
// bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127]
// Get the bloom filter value from the appropriate bucket based on the directive's bloomBit.
let value: number =
bloomBit < 64 ? (bloomBit < 32 ? di.bf0 : di.bf1) : (bloomBit < 96 ? di.bf2 : di.bf3);
bloomBit < 64 ? (bloomBit < 32 ? injector.bf0 : injector.bf1) : (bloomBit < 96 ? injector.bf2 : injector.bf3);
// If the bloom filter value has the bit corresponding to the directive's bloomBit flipped on,
// this injector is a potential match.
if ((value & mask) === mask) {
return di;
return injector;
}
// See if the parent injectors may have the value
// If the current injector does not have the directive, check the bloom filters for the ancestor
// injectors (cbf0 - cbf3). These filters capture *all* ancestor injectors.
value =
bloomBit < 64 ? (bloomBit < 32 ? di.cbf0 : di.cbf1) : (bloomBit < 96 ? di.cbf2 : di.cbf3);
// Only go to parent if parent may have value otherwise exit.
di = (value & mask) ? di.parent : null;
bloomBit < 64 ? (bloomBit < 32 ? injector.cbf0 : injector.cbf1) : (bloomBit < 96 ? injector.cbf2 : injector.cbf3);
// If the ancestor bloom filter value has the bit corresponding to the directive, traverse up to
// find the specific injector. If the ancestor bloom filter does not have the bit, we can abort.
injector = (value & mask) ? injector.parent : null;
}
return null;
}

View File

@ -17,8 +17,8 @@ import {RComment, RElement, RNode, RText, Renderer3Fn} from './renderer';
* This is necessary to add or remove elements from the DOM when a view
* is added or removed from the container. e.g. parent.removeChild(...)
*
* @param {LContainer} containerNode The container node whose parent must be found
* @returns {RNode}
* @param containerNode The container node whose parent must be found
* @returns Closest DOM node above the container
*/
export function findNativeParent(containerNode: LContainer): RNode|null {
let container: LContainer|null = containerNode;
@ -49,10 +49,10 @@ export function findNativeParent(containerNode: LContainer): RNode|null {
* the next view's first child element. Otherwise, the container's comment
* anchor is the marker.
*
* @param {number} index The index of the view to check
* @param {ContainerState} state ContainerState of the parent container
* @param {RComment} native Comment anchor for container
* @returns {RElement | RText | RComment}
* @param index The index of the view to check
* @param state ContainerState of the parent container
* @param native Comment anchor for container
* @returns The DOM element for which the view should insert elements
*/
export function findBeforeNode(index: number, state: ContainerState, native: RComment): RElement|
RText|RComment {
@ -70,10 +70,10 @@ export function findBeforeNode(index: number, state: ContainerState, native: RCo
* to propagate deeply into the nested containers to remove all elements in the
* views beneath it.
*
* @param {LContainer} container - The container to which the root view belongs
* @param {LView} rootNode - The view from which elements should be added or removed
* @param {boolean} insertMode - Whether or not elements should be added (if false, removing)
* @param {RNode} beforeNode - The node before which elements should be added, if insert mode
* @param container The container to which the root view belongs
* @param rootNode The view from which elements should be added or removed
* @param insertMode Whether or not elements should be added (if false, removing)
* @param beforeNode The node before which elements should be added, if insert mode
*/
export function addRemoveViewFromContainer(
container: LContainer, rootNode: LView, insertMode: true, beforeNode: RNode | null): void;
@ -142,7 +142,7 @@ export function addRemoveViewFromContainer(
* - Using a while loop because it's faster than recursion
* - Destroy only called on movement to sibling or movement to parent (laterally or up)
*
* @param {ViewState} rootView - The view to destroy
* @param rootView The view to destroy
*/
export function destroyViewTree(rootView: ViewState): void {
let viewOrContainerState: ViewOrContainerState|null = rootView;
@ -180,10 +180,10 @@ export function destroyViewTree(rootView: ViewState): void {
* root node of another view (in that case, the view's elements will be added when
* the container's parent view is added later).
*
* @param {LContainer} container - The container into which the view should be inserted
* @param {LView} newView - The view to insert
* @param {number} index - The index at which to insert the view
* @returns {LView} - The inserted view
* @param container The container into which the view should be inserted
* @param newView The view to insert
* @param index The index at which to insert the view
* @returns The inserted view
*/
export function insertView(container: LContainer, newView: LView, index: number): LView {
const state = container.data;
@ -226,9 +226,9 @@ export function insertView(container: LContainer, newView: LView, index: number)
* removes the view's elements from the DOM and conducts cleanup (e.g. removing
* listeners, calling onDestroys).
*
* @param {LContainer} container - The container from which to remove a view
* @param {number} removeIndex - The index of the view to remove
* @returns {LView} - The removed view
* @param container The container from which to remove a view
* @param removeIndex The index of the view to remove
* @returns The removed view
*/
export function removeView(container: LContainer, removeIndex: number): LView {
const children = container.data.children;
@ -249,8 +249,8 @@ export function removeView(container: LContainer, removeIndex: number): LView {
* one view to the next to add/remove elements. Also adds the ViewState (view.data)
* to the view tree for easy traversal when cleaning up the view.
*
* @param {LView} view - The view to set up
* @param {LView} next - The view's new next
* @param view The view to set up
* @param next The view's new next
*/
export function setViewNext(view: LView, next: LView | null): void {
view.next = next;
@ -265,9 +265,9 @@ export function setViewNext(view: LView, next: LView | null): void {
* embedded views, the container (which is the view node's parent, but not the
* ViewState's parent) needs to be checked for a possible next property.
*
* @param {ViewOrContainerState} state - The ViewOrContainerState for which we need a parent state
* @param {ViewState} rootView - The rootView, so we don't propagate too far up the view tree
* @returns {ViewOrContainerState}
* @param state The ViewOrContainerState for which we need a parent state
* @param rootView The rootView, so we don't propagate too far up the view tree
* @returns The correct parent ViewOrContainerState
*/
export function getParentState(
state: ViewOrContainerState, rootView: ViewState): ViewOrContainerState|null {
@ -286,7 +286,7 @@ export function getParentState(
/**
* Removes all listeners and call all onDestroys in a given view.
*
* @param {ViewState} viewState - The ViewState of the view to clean up
* @param viewState The ViewState of the view to clean up
*/
function cleanUpView(viewState: ViewState): void {
if (!viewState.cleanup) return;
@ -310,10 +310,10 @@ function cleanUpView(viewState: ViewState): void {
* of a parent component, the child will be appended to the right position later by
* the content projection system. Otherwise, append normally.
*
* @param {LNode} parent - The parent to which to append the child
* @param {RNode} child - The child that should be appended
* @param {ViewState} currentView - The current view's ViewState
* @returns {boolean} - Whether or not the child was appended
* @param parent The parent to which to append the child
* @param child The child that should be appended
* @param currentView The current view's ViewState
* @returns Whether or not the child was appended
*/
export function appendChild(parent: LNode, child: RNode | null, currentView: ViewState): boolean {
// Only add native child element to parent element if the parent element is regular Element.
@ -347,8 +347,8 @@ export function appendChild(parent: LNode, child: RNode | null, currentView: Vie
* of a parent component, the child will be inserted to the right position later by
* the content projection system. Otherwise, insertBefore normally.
*
* @param {LNode} node - Node to insert
* @param {ViewState} currentView - The current view's ViewState
* @param node Node to insert
* @param currentView The current view's ViewState
*/
export function insertChild(node: LNode, currentView: ViewState): void {
const parent = node.parent !;
@ -383,10 +383,10 @@ export function insertChild(node: LNode, currentView: ViewState): void {
* appends the nodes from all of the container's active views to the DOM. Also stores the
* node in the given projectedNodes array.
*
* @param {ProjectionState} projectedNodes - Array to store the projected node
* @param {LElement | LText | LContainer} node - The node to process
* @param {LView | LElement} currentParent - The last parent element to be processed
* @param {ViewState} currentView - The current view's ViewState
* @param projectedNodes Array to store the projected node
* @param node The node to process
* @param currentParent The last parent element to be processed
* @param currentView The current view's ViewState
*/
export function processProjectedNode(
projectedNodes: ProjectionState, node: LElement | LText | LContainer,