727 lines
28 KiB
TypeScript
727 lines
28 KiB
TypeScript
/**
|
|
* @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
|
|
*/
|
|
|
|
// We are temporarily importing the existing viewEngine_from core so we can be sure we are
|
|
// correctly implementing its interfaces for backwards compatibility.
|
|
import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
|
import {InjectFlags, Injector, inject, setCurrentInjector} from '../di/injector';
|
|
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
|
import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
|
|
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
|
|
import {TemplateRef as viewEngine_TemplateRef} from '../linker/template_ref';
|
|
import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref';
|
|
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref';
|
|
import {Type} from '../type';
|
|
|
|
import {assertDefined, assertGreaterThan, assertLessThan} from './assert';
|
|
import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTNode, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions';
|
|
import {VIEWS} from './interfaces/container';
|
|
import {ComponentTemplate, DirectiveDef} from './interfaces/definition';
|
|
import {LInjector} from './interfaces/injector';
|
|
import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node';
|
|
import {LQueries, QueryReadType} from './interfaces/query';
|
|
import {Renderer3} from './interfaces/renderer';
|
|
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
|
|
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
|
import {appendChild, detachView, getParentLNode, insertView, removeView} from './node_manipulation';
|
|
import {notImplemented, stringify} from './util';
|
|
import {EmbeddedViewRef, ViewRef} from './view_ref';
|
|
|
|
|
|
|
|
/**
|
|
* If a directive is diPublic, bloomAdd sets a property on the instance with this constant as
|
|
* the key and the directive's unique ID as the value. This allows us to map directives to their
|
|
* bloom filter bit for DI.
|
|
*/
|
|
const NG_ELEMENT_ID = '__NG_ELEMENT_ID__';
|
|
|
|
/**
|
|
* The number of slots in each bloom filter (used by DI). The larger this number, the fewer
|
|
* directives that will share slots, and thus, the fewer false positives when checking for
|
|
* the existence of a directive.
|
|
*/
|
|
const BLOOM_SIZE = 256;
|
|
|
|
/** Counter used to generate unique IDs for directives. */
|
|
let nextNgElementId = 0;
|
|
|
|
/**
|
|
* Registers this directive as present in its node's injector by flipping the directive's
|
|
* corresponding bit in the injector's bloom filter.
|
|
*
|
|
* @param injector The node injector in which the directive should be registered
|
|
* @param type The directive to register
|
|
*/
|
|
export function bloomAdd(injector: LInjector, type: Type<any>): void {
|
|
let id: number|undefined = (type as any)[NG_ELEMENT_ID];
|
|
|
|
// Set a unique ID on the directive type, so if something tries to inject the directive,
|
|
// we can easily retrieve the ID and hash it into the bloom bit that should be checked.
|
|
if (id == null) {
|
|
id = (type as any)[NG_ELEMENT_ID] = nextNgElementId++;
|
|
}
|
|
|
|
// We only have BLOOM_SIZE (256) slots in our bloom filter (8 buckets * 32 bits each),
|
|
// so all unique IDs must be modulo-ed into a number from 0 - 255 to fit into the filter.
|
|
// This means that after 255, some directives will share slots, leading to some false positives
|
|
// when checking for a directive's presence.
|
|
const bloomBit = id % BLOOM_SIZE;
|
|
|
|
// Create a mask that targets the specific bit associated with the directive.
|
|
// 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.
|
|
const mask = 1 << bloomBit;
|
|
|
|
// Use the raw bloomBit number to determine which bloom filter bucket we should check
|
|
// e.g: bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc
|
|
if (bloomBit < 128) {
|
|
// Then use the mask to flip on the bit (0-31) associated with the directive in that bucket
|
|
bloomBit < 64 ? (bloomBit < 32 ? (injector.bf0 |= mask) : (injector.bf1 |= mask)) :
|
|
(bloomBit < 96 ? (injector.bf2 |= mask) : (injector.bf3 |= mask));
|
|
} else {
|
|
bloomBit < 192 ? (bloomBit < 160 ? (injector.bf4 |= mask) : (injector.bf5 |= mask)) :
|
|
(bloomBit < 224 ? (injector.bf6 |= mask) : (injector.bf7 |= mask));
|
|
}
|
|
}
|
|
|
|
export function getOrCreateNodeInjector(): LInjector {
|
|
ngDevMode && assertPreviousIsParent();
|
|
return getOrCreateNodeInjectorForNode(getPreviousOrParentNode() as LElementNode | LContainerNode);
|
|
}
|
|
|
|
/**
|
|
* Creates (or gets an existing) injector for a given element or container.
|
|
*
|
|
* @param node for which an injector should be retrieved / created.
|
|
* @returns Node injector
|
|
*/
|
|
export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNode): LInjector {
|
|
const nodeInjector = node.nodeInjector;
|
|
const parent = getParentLNode(node);
|
|
const parentInjector = parent && parent.nodeInjector;
|
|
if (nodeInjector != parentInjector) {
|
|
return nodeInjector !;
|
|
}
|
|
return node.nodeInjector = {
|
|
parent: parentInjector,
|
|
node: node,
|
|
bf0: 0,
|
|
bf1: 0,
|
|
bf2: 0,
|
|
bf3: 0,
|
|
bf4: 0,
|
|
bf5: 0,
|
|
bf6: 0,
|
|
bf7: 0,
|
|
cbf0: parentInjector == null ? 0 : parentInjector.cbf0 | parentInjector.bf0,
|
|
cbf1: parentInjector == null ? 0 : parentInjector.cbf1 | parentInjector.bf1,
|
|
cbf2: parentInjector == null ? 0 : parentInjector.cbf2 | parentInjector.bf2,
|
|
cbf3: parentInjector == null ? 0 : parentInjector.cbf3 | parentInjector.bf3,
|
|
cbf4: parentInjector == null ? 0 : parentInjector.cbf4 | parentInjector.bf4,
|
|
cbf5: parentInjector == null ? 0 : parentInjector.cbf5 | parentInjector.bf5,
|
|
cbf6: parentInjector == null ? 0 : parentInjector.cbf6 | parentInjector.bf6,
|
|
cbf7: parentInjector == null ? 0 : parentInjector.cbf7 | parentInjector.bf7,
|
|
templateRef: null,
|
|
viewContainerRef: null,
|
|
elementRef: null,
|
|
changeDetectorRef: null
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* Makes a directive public to the DI system by adding it to an injector's bloom filter.
|
|
*
|
|
* @param di The node injector in which a directive will be added
|
|
* @param def The definition of the directive to be made public
|
|
*/
|
|
export function diPublicInInjector(di: LInjector, def: DirectiveDef<any>): void {
|
|
bloomAdd(di, def.type);
|
|
}
|
|
|
|
/**
|
|
* Makes a directive public to the DI system by adding it to an injector's bloom filter.
|
|
*
|
|
* @param def The definition of the directive to be made public
|
|
*/
|
|
export function diPublic(def: DirectiveDef<any>): void {
|
|
diPublicInInjector(getOrCreateNodeInjector(), def);
|
|
}
|
|
|
|
/**
|
|
* Searches for an instance of the given type up the injector tree and returns
|
|
* that instance if found.
|
|
*
|
|
* 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(directiveInject(DirectiveA))
|
|
* });
|
|
* }
|
|
*
|
|
* NOTE: use `directiveInject` with `@Directive`, `@Component`, and `@Pipe`. For
|
|
* all other injection use `inject` which does not walk the DOM render tree.
|
|
*
|
|
* @param token The directive type to search for
|
|
* @param flags Injection flags (e.g. CheckParent)
|
|
* @returns The instance found
|
|
*/
|
|
export function directiveInject<T>(token: Type<T>): T;
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
return getOrCreateElementRef(getOrCreateNodeInjector());
|
|
}
|
|
|
|
/**
|
|
* 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<T>(): viewEngine_TemplateRef<T> {
|
|
return getOrCreateTemplateRef<T>(getOrCreateNodeInjector());
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
return getOrCreateContainerRef(getOrCreateNodeInjector());
|
|
}
|
|
|
|
/** Returns a ChangeDetectorRef (a.k.a. a ViewRef) */
|
|
export function injectChangeDetectorRef(): viewEngine_ChangeDetectorRef {
|
|
return getOrCreateChangeDetectorRef(getOrCreateNodeInjector(), null);
|
|
}
|
|
|
|
/**
|
|
* Inject static attribute value into directive constructor.
|
|
*
|
|
* This method is used with `factory` functions which are generated as part of
|
|
* `defineDirective` or `defineComponent`. The method retrieves the static value
|
|
* of an attribute. (Dynamic attributes are not supported since they are not resolved
|
|
* at the time of injection and can change over time.)
|
|
*
|
|
* # Example
|
|
* Given:
|
|
* ```
|
|
* @Component(...)
|
|
* class MyComponent {
|
|
* constructor(@Attribute('title') title: string) { ... }
|
|
* }
|
|
* ```
|
|
* When instantiated with
|
|
* ```
|
|
* <my-component title="Hello"></my-component>
|
|
* ```
|
|
*
|
|
* Then factory method generated is:
|
|
* ```
|
|
* MyComponent.ngComponentDef = defineComponent({
|
|
* factory: () => new MyComponent(injectAttribute('title'))
|
|
* ...
|
|
* })
|
|
* ```
|
|
*
|
|
* @experimental
|
|
*/
|
|
export function injectAttribute(attrNameToInject: string): string|undefined {
|
|
ngDevMode && assertPreviousIsParent();
|
|
const lElement = getPreviousOrParentNode() as LElementNode;
|
|
ngDevMode && assertNodeType(lElement, TNodeType.Element);
|
|
const tElement = lElement.tNode;
|
|
ngDevMode && assertDefined(tElement, 'expecting tNode');
|
|
const attrs = tElement.attrs;
|
|
if (attrs) {
|
|
for (let i = 0; i < attrs.length; i = i + 2) {
|
|
const attrName = attrs[i];
|
|
if (attrName === AttributeMarker.SELECT_ONLY) break;
|
|
if (attrName == attrNameToInject) {
|
|
return attrs[i + 1] as string;
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Creates a ViewRef and stores it on the injector as ChangeDetectorRef (public alias).
|
|
* Or, if it already exists, retrieves the existing instance.
|
|
*
|
|
* @returns The ChangeDetectorRef to use
|
|
*/
|
|
export function getOrCreateChangeDetectorRef(
|
|
di: LInjector, context: any): viewEngine_ChangeDetectorRef {
|
|
if (di.changeDetectorRef) return di.changeDetectorRef;
|
|
|
|
const currentNode = di.node;
|
|
if (isComponent(currentNode.tNode)) {
|
|
return di.changeDetectorRef = new ViewRef(currentNode.data as LViewData, context);
|
|
} else if (currentNode.tNode.type === TNodeType.Element) {
|
|
return di.changeDetectorRef = getOrCreateHostChangeDetector(currentNode.view[HOST_NODE]);
|
|
}
|
|
return null !;
|
|
}
|
|
|
|
/** Gets or creates ChangeDetectorRef for the closest host component */
|
|
function getOrCreateHostChangeDetector(currentNode: LViewNode | LElementNode):
|
|
viewEngine_ChangeDetectorRef {
|
|
const hostNode = getClosestComponentAncestor(currentNode);
|
|
const hostInjector = hostNode.nodeInjector;
|
|
const existingRef = hostInjector && hostInjector.changeDetectorRef;
|
|
|
|
return existingRef ?
|
|
existingRef :
|
|
new ViewRef(
|
|
hostNode.data as LViewData,
|
|
hostNode
|
|
.view[DIRECTIVES] ![hostNode.tNode.flags >> TNodeFlags.DirectiveStartingIndexShift]);
|
|
}
|
|
|
|
/**
|
|
* If the node is an embedded view, traverses up the view tree to return the closest
|
|
* ancestor view that is attached to a component. If it's already a component node,
|
|
* returns itself.
|
|
*/
|
|
function getClosestComponentAncestor(node: LViewNode | LElementNode): LElementNode {
|
|
while (node.tNode.type === TNodeType.View) {
|
|
node = node.view[HOST_NODE];
|
|
}
|
|
return node as LElementNode;
|
|
}
|
|
|
|
/**
|
|
* 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 di Node injector where the search should start
|
|
* @param token The directive type to search for
|
|
* @param flags Injection flags (e.g. CheckParent)
|
|
* @returns The instance found
|
|
*/
|
|
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
|
|
// (diPublic). If there is no hash, fall back to the module injector.
|
|
if (bloomHash === null) {
|
|
const moduleInjector = getPreviousOrParentNode().view[INJECTOR];
|
|
const formerInjector = setCurrentInjector(moduleInjector);
|
|
try {
|
|
return inject(token, flags);
|
|
} finally {
|
|
setCurrentInjector(formerInjector);
|
|
}
|
|
} else {
|
|
let injector: LInjector|null = di;
|
|
|
|
while (injector) {
|
|
// Get the closest potential matching injector (upwards in the injector tree) that
|
|
// *potentially* has the token.
|
|
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.
|
|
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;
|
|
const nodeFlags = node.tNode.flags;
|
|
const count = nodeFlags & TNodeFlags.DirectiveCountMask;
|
|
|
|
if (count !== 0) {
|
|
const start = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
|
|
const end = start + count;
|
|
const defs = node.view[TVIEW].directives !;
|
|
|
|
for (let i = start; i < end; 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 = defs[i] as DirectiveDef<any>;
|
|
if (directiveDef.type === token && directiveDef.diPublic) {
|
|
return getDirectiveInstance(node.view[DIRECTIVES] ![i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we *didn't* find the directive for the token and we are searching the current node's
|
|
// injector, it's possible the directive is on this node and hasn't been created yet.
|
|
let instance: T|null;
|
|
if (injector === di && (instance = searchMatchesQueuedForCreation<T>(node, token))) {
|
|
return instance;
|
|
}
|
|
|
|
// 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.
|
|
if (flags & InjectFlags.Optional) return null;
|
|
throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
|
|
}
|
|
|
|
function searchMatchesQueuedForCreation<T>(node: LNode, token: any): T|null {
|
|
const matches = node.view[TVIEW].currentMatches;
|
|
if (matches) {
|
|
for (let i = 0; i < matches.length; i += 2) {
|
|
const def = matches[i] as DirectiveDef<any>;
|
|
if (def.type === token) {
|
|
return resolveDirective(def, i + 1, matches, node.view[TVIEW]);
|
|
}
|
|
}
|
|
}
|
|
return 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: Type<any>): 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 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
|
|
* 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 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, 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.
|
|
const mask = 1 << bloomBit;
|
|
|
|
// Traverse up the injector tree until we find a potential match or until we know there *isn't* a
|
|
// match.
|
|
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.
|
|
// Get the bloom filter value from the appropriate bucket based on the directive's bloomBit.
|
|
let value: number;
|
|
if (bloomBit < 128) {
|
|
value = bloomBit < 64 ? (bloomBit < 32 ? injector.bf0 : injector.bf1) :
|
|
(bloomBit < 96 ? injector.bf2 : injector.bf3);
|
|
} else {
|
|
value = bloomBit < 192 ? (bloomBit < 160 ? injector.bf4 : injector.bf5) :
|
|
(bloomBit < 224 ? injector.bf6 : injector.bf7);
|
|
}
|
|
|
|
// 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 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
|
|
// injectors (cbf0 - cbf7). These filters capture *all* ancestor injectors.
|
|
if (bloomBit < 128) {
|
|
value = bloomBit < 64 ? (bloomBit < 32 ? injector.cbf0 : injector.cbf1) :
|
|
(bloomBit < 96 ? injector.cbf2 : injector.cbf3);
|
|
} else {
|
|
value = bloomBit < 192 ? (bloomBit < 160 ? injector.cbf4 : injector.cbf5) :
|
|
(bloomBit < 224 ? injector.cbf6 : injector.cbf7);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
/**
|
|
* 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) {}
|
|
}
|
|
|
|
/**
|
|
* Creates an ElementRef for a given node injector and stores it on the injector.
|
|
* Or, if the ElementRef already exists, retrieves the existing ElementRef.
|
|
*
|
|
* @param di The node injector where we should store a created ElementRef
|
|
* @returns The ElementRef instance to use
|
|
*/
|
|
export function getOrCreateElementRef(di: LInjector): viewEngine_ElementRef {
|
|
return di.elementRef || (di.elementRef = new ElementRef(di.node.native));
|
|
}
|
|
|
|
export const QUERY_READ_TEMPLATE_REF = <QueryReadType<viewEngine_TemplateRef<any>>>(
|
|
new ReadFromInjectorFn<viewEngine_TemplateRef<any>>(
|
|
(injector: LInjector) => getOrCreateTemplateRef(injector)) as any);
|
|
|
|
export const QUERY_READ_CONTAINER_REF = <QueryReadType<viewEngine_ViewContainerRef>>(
|
|
new ReadFromInjectorFn<viewEngine_ViewContainerRef>(
|
|
(injector: LInjector) => getOrCreateContainerRef(injector)) as any);
|
|
|
|
export const QUERY_READ_ELEMENT_REF =
|
|
<QueryReadType<viewEngine_ElementRef>>(new ReadFromInjectorFn<viewEngine_ElementRef>(
|
|
(injector: LInjector) => getOrCreateElementRef(injector)) as any);
|
|
|
|
export const QUERY_READ_FROM_NODE =
|
|
(new ReadFromInjectorFn<any>((injector: LInjector, node: LNode, directiveIdx: number) => {
|
|
ngDevMode && assertNodeOfPossibleTypes(node, TNodeType.Container, TNodeType.Element);
|
|
if (directiveIdx > -1) {
|
|
return node.view[DIRECTIVES] ![directiveIdx];
|
|
} else if (node.tNode.type === TNodeType.Element) {
|
|
return getOrCreateElementRef(injector);
|
|
} else if (node.tNode.type === TNodeType.Container) {
|
|
return getOrCreateTemplateRef(injector);
|
|
}
|
|
throw new Error('fail');
|
|
}) as any as QueryReadType<any>);
|
|
|
|
/** A ref to a node's native element. */
|
|
class ElementRef implements viewEngine_ElementRef {
|
|
readonly nativeElement: any;
|
|
constructor(nativeElement: any) { this.nativeElement = nativeElement; }
|
|
}
|
|
|
|
/**
|
|
* 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 getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainerRef {
|
|
if (!di.viewContainerRef) {
|
|
const vcRefHost = di.node;
|
|
|
|
ngDevMode && assertNodeOfPossibleTypes(vcRefHost, TNodeType.Container, TNodeType.Element);
|
|
const hostParent = getParentLNode(vcRefHost) !;
|
|
const lContainer = createLContainer(hostParent, vcRefHost.view, true);
|
|
const comment = vcRefHost.view[RENDERER].createComment(ngDevMode ? 'container' : '');
|
|
const lContainerNode: LContainerNode = createLNodeObject(
|
|
TNodeType.Container, vcRefHost.view, hostParent, comment, lContainer, null);
|
|
appendChild(hostParent, comment, vcRefHost.view);
|
|
|
|
|
|
if (vcRefHost.queries) {
|
|
lContainerNode.queries = vcRefHost.queries.container();
|
|
}
|
|
|
|
const hostTNode = vcRefHost.tNode;
|
|
if (!hostTNode.dynamicContainerNode) {
|
|
hostTNode.dynamicContainerNode = createTNode(TNodeType.Container, -1, null, null, null, null);
|
|
}
|
|
|
|
lContainerNode.tNode = hostTNode.dynamicContainerNode;
|
|
vcRefHost.dynamicLContainerNode = lContainerNode;
|
|
|
|
addToViewTree(vcRefHost.view, hostTNode.index as number, lContainer);
|
|
|
|
di.viewContainerRef = new ViewContainerRef(lContainerNode);
|
|
}
|
|
|
|
return di.viewContainerRef;
|
|
}
|
|
|
|
/**
|
|
* A ref to a container that enables adding and removing views from that container
|
|
* imperatively.
|
|
*/
|
|
class ViewContainerRef implements viewEngine_ViewContainerRef {
|
|
private _viewRefs: viewEngine_ViewRef[] = [];
|
|
element: viewEngine_ElementRef;
|
|
injector: Injector;
|
|
parentInjector: Injector;
|
|
|
|
constructor(private _lContainerNode: LContainerNode) {}
|
|
|
|
clear(): void {
|
|
const lContainer = this._lContainerNode.data;
|
|
while (lContainer[VIEWS].length) {
|
|
this.remove(0);
|
|
}
|
|
}
|
|
|
|
get(index: number): viewEngine_ViewRef|null { return this._viewRefs[index] || null; }
|
|
|
|
get length(): number {
|
|
const lContainer = this._lContainerNode.data;
|
|
return lContainer[VIEWS].length;
|
|
}
|
|
|
|
createEmbeddedView<C>(templateRef: viewEngine_TemplateRef<C>, context?: C, index?: number):
|
|
viewEngine_EmbeddedViewRef<C> {
|
|
const viewRef = templateRef.createEmbeddedView(context || <any>{});
|
|
this.insert(viewRef, index);
|
|
return viewRef;
|
|
}
|
|
|
|
createComponent<C>(
|
|
componentFactory: viewEngine_ComponentFactory<C>, index?: number|undefined,
|
|
injector?: Injector|undefined, projectableNodes?: any[][]|undefined,
|
|
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<C> {
|
|
throw notImplemented();
|
|
}
|
|
|
|
insert(viewRef: viewEngine_ViewRef, index?: number): viewEngine_ViewRef {
|
|
if (viewRef.destroyed) {
|
|
throw new Error('Cannot insert a destroyed View in a ViewContainer!');
|
|
}
|
|
const lViewNode = (viewRef as EmbeddedViewRef<any>)._lViewNode;
|
|
const adjustedIdx = this._adjustIndex(index);
|
|
|
|
(viewRef as EmbeddedViewRef<any>).attachToViewContainerRef(this);
|
|
|
|
insertView(this._lContainerNode, lViewNode, adjustedIdx);
|
|
|
|
this._viewRefs.splice(adjustedIdx, 0, viewRef);
|
|
|
|
return viewRef;
|
|
}
|
|
|
|
move(viewRef: viewEngine_ViewRef, newIndex: number): viewEngine_ViewRef {
|
|
const index = this.indexOf(viewRef);
|
|
this.detach(index);
|
|
this.insert(viewRef, this._adjustIndex(newIndex));
|
|
return viewRef;
|
|
}
|
|
|
|
indexOf(viewRef: viewEngine_ViewRef): number { return this._viewRefs.indexOf(viewRef); }
|
|
|
|
remove(index?: number): void {
|
|
const adjustedIdx = this._adjustIndex(index, -1);
|
|
removeView(this._lContainerNode, adjustedIdx);
|
|
this._viewRefs.splice(adjustedIdx, 1);
|
|
}
|
|
|
|
detach(index?: number): viewEngine_ViewRef|null {
|
|
const adjustedIdx = this._adjustIndex(index, -1);
|
|
detachView(this._lContainerNode, adjustedIdx);
|
|
return this._viewRefs.splice(adjustedIdx, 1)[0] || null;
|
|
}
|
|
|
|
private _adjustIndex(index?: number, shift: number = 0) {
|
|
if (index == null) {
|
|
return this._lContainerNode.data[VIEWS].length + shift;
|
|
}
|
|
if (ngDevMode) {
|
|
assertGreaterThan(index, -1, 'index must be positive');
|
|
// +1 because it's legal to insert at the end.
|
|
assertLessThan(index, this._lContainerNode.data[VIEWS].length + 1 + shift, 'index');
|
|
}
|
|
return index;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a TemplateRef and stores it on the injector. Or, if the TemplateRef already
|
|
* exists, retrieves the existing TemplateRef.
|
|
*
|
|
* @param di The node injector where we should store a created TemplateRef
|
|
* @returns The TemplateRef instance to use
|
|
*/
|
|
export function getOrCreateTemplateRef<T>(di: LInjector): viewEngine_TemplateRef<T> {
|
|
if (!di.templateRef) {
|
|
ngDevMode && assertNodeType(di.node, TNodeType.Container);
|
|
const hostNode = di.node as LContainerNode;
|
|
const hostTNode = hostNode.tNode;
|
|
ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated');
|
|
di.templateRef = new TemplateRef<any>(
|
|
getOrCreateElementRef(di), hostTNode.tViews as TView, getRenderer(),
|
|
hostNode.data[QUERIES]);
|
|
}
|
|
return di.templateRef;
|
|
}
|
|
|
|
class TemplateRef<T> implements viewEngine_TemplateRef<T> {
|
|
readonly elementRef: viewEngine_ElementRef;
|
|
|
|
constructor(
|
|
elementRef: viewEngine_ElementRef, private _tView: TView, private _renderer: Renderer3,
|
|
private _queries: LQueries|null) {
|
|
this.elementRef = elementRef;
|
|
}
|
|
|
|
createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> {
|
|
const viewNode =
|
|
renderEmbeddedTemplate(null, this._tView, context, this._renderer, this._queries);
|
|
return new EmbeddedViewRef(viewNode, this._tView.template !as ComponentTemplate<T>, context);
|
|
}
|
|
}
|