feat(ivy): allow combined context discovery for components, directives and elements (#25754)
PR Close #25754
This commit is contained in:
parent
d2dfd48be0
commit
62be8c2e2f
|
@ -162,9 +162,9 @@ export {
|
|||
} from './sanitization/bypass';
|
||||
|
||||
export {
|
||||
ElementContext as ɵElementContext,
|
||||
getElementContext as ɵgetElementContext
|
||||
} from './render3/element_discovery';
|
||||
LContext as ɵLContext,
|
||||
getContext as ɵgetContext
|
||||
} from './render3/context_discovery';
|
||||
|
||||
// we reexport these symbols just so that they are retained during the dead code elimination
|
||||
// performed by rollup while it's creating fesm files.
|
||||
|
|
|
@ -0,0 +1,396 @@
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
import './ng_dev_mode';
|
||||
|
||||
import {assertEqual} from './assert';
|
||||
import {LElementNode, TNode, TNodeFlags} from './interfaces/node';
|
||||
import {RElement} from './interfaces/renderer';
|
||||
import {CONTEXT, DIRECTIVES, HEADER_OFFSET, LViewData, TVIEW} from './interfaces/view';
|
||||
import {readElementValue} from './util';
|
||||
|
||||
/**
|
||||
* This property will be monkey-patched on elements, components and directives
|
||||
*/
|
||||
export const MONKEY_PATCH_KEY_NAME = '__ngContext__';
|
||||
|
||||
/**
|
||||
* The internal view context which is specific to a given DOM element, directive or
|
||||
* component instance. Each value in here (besides the LViewData and element node details)
|
||||
* can be present, null or undefined. If undefined then it implies the value has not been
|
||||
* looked up yet, otherwise, if null, then a lookup was executed and nothing was found.
|
||||
*
|
||||
* Each value will get filled when the respective value is examined within the getContext
|
||||
* function. The component, element and each directive instance will share the same instance
|
||||
* of the context.
|
||||
*/
|
||||
export interface LContext {
|
||||
/** The component\'s view data */
|
||||
lViewData: LViewData;
|
||||
|
||||
/** The index instance of the LNode */
|
||||
lNodeIndex: number;
|
||||
|
||||
/** The instance of the DOM node that is attached to the lNode */
|
||||
native: RElement;
|
||||
|
||||
/** The instance of the Component node */
|
||||
component: {}|null|undefined;
|
||||
|
||||
/** The list of indices for the active directives that exist on this element */
|
||||
directiveIndices: number[]|null|undefined;
|
||||
|
||||
/** The list of active directives that exist on this element */
|
||||
directives: Array<{}>|null|undefined;
|
||||
}
|
||||
|
||||
/** Returns the matching `LContext` data for a given DOM node, directive or component instance.
|
||||
*
|
||||
* This function will examine the provided DOM element, component, or directive instance\'s
|
||||
* monkey-patched property to derive the `LContext` data. Once called then the monkey-patched
|
||||
* value will be that of the newly created `LContext`.
|
||||
*
|
||||
* If the monkey-patched value is the `LViewData` instance then the context value for that
|
||||
* target will be created and the monkey-patch reference will be updated. Therefore when this
|
||||
* function is called it may mutate the provided element\'s, component\'s or any of the associated
|
||||
* directive\'s monkey-patch values.
|
||||
*
|
||||
* If the monkey-patch value is not detected then the code will walk up the DOM until an element
|
||||
* is found which contains a monkey-patch reference. When that occurs then the provided element
|
||||
* will be updated with a new context (which is then returned). If the monkey-patch value is not
|
||||
* detected for a component/directive instance then it will throw an error (all components and
|
||||
* directives should be automatically monkey-patched by ivy).
|
||||
*/
|
||||
export function getContext(target: any): LContext|null {
|
||||
let mpValue = readPatchedData(target);
|
||||
if (mpValue) {
|
||||
// only when it's an array is it considered an LViewData instance
|
||||
// ... otherwise it's an already constructed LContext instance
|
||||
if (Array.isArray(mpValue)) {
|
||||
const lViewData: LViewData = mpValue !;
|
||||
let lNodeIndex: number;
|
||||
let component: any = undefined;
|
||||
let directiveIndices: number[]|null|undefined = undefined;
|
||||
let directives: any[]|null|undefined = undefined;
|
||||
|
||||
if (isComponentInstance(target)) {
|
||||
lNodeIndex = findViaComponent(lViewData, target);
|
||||
if (lNodeIndex == -1) {
|
||||
throw new Error('The provided component was not found in the application');
|
||||
}
|
||||
component = target;
|
||||
} else if (isDirectiveInstance(target)) {
|
||||
lNodeIndex = findViaDirective(lViewData, target);
|
||||
if (lNodeIndex == -1) {
|
||||
throw new Error('The provided directive was not found in the application');
|
||||
}
|
||||
directiveIndices = discoverDirectiveIndices(lViewData, lNodeIndex);
|
||||
directives = directiveIndices ? discoverDirectives(lViewData, directiveIndices) : null;
|
||||
} else {
|
||||
lNodeIndex = findViaNativeElement(lViewData, target as RElement);
|
||||
if (lNodeIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// the goal is not to fill the entire context full of data because the lookups
|
||||
// are expensive. Instead, only the target data (the element, compontent or
|
||||
// directive details) are filled into the context. If called multiple times
|
||||
// with different target values then the missing target data will be filled in.
|
||||
const lNode = getLNodeFromViewData(lViewData, lNodeIndex) !;
|
||||
const existingCtx = readPatchedData(lNode.native);
|
||||
const context: LContext = (existingCtx && !Array.isArray(existingCtx)) ?
|
||||
existingCtx :
|
||||
createLContext(lViewData, lNodeIndex, lNode.native);
|
||||
|
||||
// only when the component has been discovered then update the monkey-patch
|
||||
if (component && context.component === undefined) {
|
||||
context.component = component;
|
||||
attachPatchData(context.component, context);
|
||||
}
|
||||
|
||||
// only when the directives have been discovered then update the monkey-patch
|
||||
if (directives && directiveIndices && context.directives === undefined) {
|
||||
context.directiveIndices = directiveIndices;
|
||||
context.directives = directives;
|
||||
for (let i = 0; i < directives.length; i++) {
|
||||
attachPatchData(directives[i], context);
|
||||
}
|
||||
}
|
||||
|
||||
attachPatchData(context.native, context);
|
||||
mpValue = context;
|
||||
}
|
||||
} else {
|
||||
const rElement = target as RElement;
|
||||
ngDevMode && assertDomElement(rElement);
|
||||
|
||||
// if the context is not found then we need to traverse upwards up the DOM
|
||||
// to find the nearest element that has already been monkey patched with data
|
||||
let parent = rElement as any;
|
||||
while (parent = parent.parentNode) {
|
||||
const parentContext = readPatchedData(parent);
|
||||
if (parentContext) {
|
||||
let lViewData: LViewData|null;
|
||||
if (Array.isArray(parentContext)) {
|
||||
lViewData = parentContext as LViewData;
|
||||
} else {
|
||||
lViewData = parentContext.lViewData;
|
||||
}
|
||||
|
||||
// the edge of the app was also reached here through another means
|
||||
// (maybe because the DOM was changed manually).
|
||||
if (!lViewData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = findViaNativeElement(lViewData, rElement);
|
||||
if (index >= 0) {
|
||||
const lNode = getLNodeFromViewData(lViewData, index) !;
|
||||
const context = createLContext(lViewData, index, lNode.native);
|
||||
attachPatchData(lNode.native, context);
|
||||
mpValue = context;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (mpValue as LContext) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty instance of a `LContext` context
|
||||
*/
|
||||
function createLContext(lViewData: LViewData, lNodeIndex: number, native: RElement): LContext {
|
||||
return {
|
||||
lViewData,
|
||||
lNodeIndex,
|
||||
native,
|
||||
component: undefined,
|
||||
directiveIndices: undefined,
|
||||
directives: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility function for retrieving the matching lElementNode
|
||||
* from a given DOM element, component or directive.
|
||||
*/
|
||||
export function getLElementNode(target: any): LElementNode|null {
|
||||
const context = getContext(target);
|
||||
return context ? getLNodeFromViewData(context.lViewData, context.lNodeIndex) : null;
|
||||
}
|
||||
|
||||
export function getLElementFromRootComponent(componentInstance: {}): LElementNode|null {
|
||||
// the host element for the root component is ALWAYS the first element
|
||||
// in the lViewData array (which is where HEADER_OFFSET points to)
|
||||
return getLElementFromComponent(componentInstance, HEADER_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simplified lookup function for finding the LElementNode from a component instance.
|
||||
*
|
||||
* This function exists for tree-shaking purposes to avoid having to pull in everything
|
||||
* that `getContext` has in the event that an Angular application doesn't need to have
|
||||
* any programmatic access to an element's context (only change detection uses this function).
|
||||
*/
|
||||
export function getLElementFromComponent(
|
||||
componentInstance: {}, expectedLNodeIndex?: number): LElementNode|null {
|
||||
let lViewData = readPatchedData(componentInstance);
|
||||
let lNode: LElementNode;
|
||||
|
||||
if (Array.isArray(lViewData)) {
|
||||
expectedLNodeIndex = expectedLNodeIndex || findViaComponent(lViewData, componentInstance);
|
||||
lNode = readElementValue(lViewData[expectedLNodeIndex]);
|
||||
const context = createLContext(lViewData, expectedLNodeIndex, lNode.native);
|
||||
context.component = componentInstance;
|
||||
attachPatchData(componentInstance, context);
|
||||
attachPatchData(context.native, context);
|
||||
} else {
|
||||
const context = lViewData as any as LContext;
|
||||
lNode = readElementValue(context.lViewData[context.lNodeIndex]);
|
||||
}
|
||||
|
||||
return lNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the given data to the given target (which could be a component,
|
||||
* directive or DOM node instance) using monkey-patching.
|
||||
*/
|
||||
export function attachPatchData(target: any, data: LViewData | LContext) {
|
||||
target[MONKEY_PATCH_KEY_NAME] = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the monkey-patch value data present on the target (which could be
|
||||
* a component, directive or a DOM node).
|
||||
*/
|
||||
export function readPatchedData(target: any): LViewData|LContext|null {
|
||||
return target[MONKEY_PATCH_KEY_NAME];
|
||||
}
|
||||
|
||||
export function isComponentInstance(instance: any): boolean {
|
||||
return instance && instance.constructor && instance.constructor.ngComponentDef;
|
||||
}
|
||||
|
||||
export function isDirectiveInstance(instance: any): boolean {
|
||||
return instance && instance.constructor && instance.constructor.ngDirectiveDef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the element within the given LViewData and returns the matching index
|
||||
*/
|
||||
function findViaNativeElement(lViewData: LViewData, native: RElement): number {
|
||||
let tNode = lViewData[TVIEW].firstChild;
|
||||
while (tNode) {
|
||||
const lNode = getLNodeFromViewData(lViewData, tNode.index) !;
|
||||
if (lNode.native === native) {
|
||||
return tNode.index;
|
||||
}
|
||||
tNode = traverseNextElement(tNode);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the next tNode (child, sibling or parent).
|
||||
*/
|
||||
function traverseNextElement(tNode: TNode): TNode|null {
|
||||
if (tNode.child) {
|
||||
return tNode.child;
|
||||
} else if (tNode.next) {
|
||||
return tNode.next;
|
||||
} else if (tNode.parent) {
|
||||
return tNode.parent.next || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the component within the given LViewData and returns the matching index
|
||||
*/
|
||||
function findViaComponent(lViewData: LViewData, componentInstance: {}): number {
|
||||
const componentIndices = lViewData[TVIEW].components;
|
||||
if (componentIndices) {
|
||||
for (let i = 0; i < componentIndices.length; i++) {
|
||||
const elementComponentIndex = componentIndices[i];
|
||||
const lNodeData = readElementValue(lViewData[elementComponentIndex] !).data !;
|
||||
if (lNodeData[CONTEXT] === componentInstance) {
|
||||
return elementComponentIndex;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const rootNode = lViewData[HEADER_OFFSET];
|
||||
const rootComponent = rootNode.data[CONTEXT];
|
||||
if (rootComponent === componentInstance) {
|
||||
// we are dealing with the root element here therefore we know that the
|
||||
// element is the very first element after the HEADER data in the lView
|
||||
return HEADER_OFFSET;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the directive within the given LViewData and returns the matching index
|
||||
*/
|
||||
function findViaDirective(lViewData: LViewData, directiveInstance: {}): number {
|
||||
// if a directive is monkey patched then it will (by default)
|
||||
// have a reference to the LViewData of the current view. The
|
||||
// element bound to the directive being search lives somewhere
|
||||
// in the view data. By first checking to see if the instance
|
||||
// is actually present we can narrow down to which lElementNode
|
||||
// contains the instance of the directive and then return the index
|
||||
const directivesAcrossView = lViewData[DIRECTIVES];
|
||||
const directiveIndex =
|
||||
directivesAcrossView ? directivesAcrossView.indexOf(directiveInstance) : -1;
|
||||
if (directiveIndex >= 0) {
|
||||
let tNode = lViewData[TVIEW].firstChild;
|
||||
while (tNode) {
|
||||
const lNode = getLNodeFromViewData(lViewData, tNode.index) !;
|
||||
const directiveIndexStart = getDirectiveStartIndex(lNode);
|
||||
const directiveIndexEnd = getDirectiveEndIndex(lNode, directiveIndexStart);
|
||||
if (directiveIndex >= directiveIndexStart && directiveIndex < directiveIndexEnd) {
|
||||
return tNode.index;
|
||||
}
|
||||
tNode = traverseNextElement(tNode);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
function assertDomElement(element: any) {
|
||||
assertEqual(element.nodeType, 1, 'The provided value must be an instance of an HTMLElement');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retruns the instance of the LElementNode at the given index in the LViewData.
|
||||
*
|
||||
* This function will also unwrap the inner value incase it's stuffed into an
|
||||
* array (which is what happens when [style] and [class] bindings are present
|
||||
* in the view instructions for the element being returned).
|
||||
*/
|
||||
function getLNodeFromViewData(lViewData: LViewData, lElementIndex: number): LElementNode|null {
|
||||
const value = lViewData[lElementIndex];
|
||||
return value ? readElementValue(value) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of directive index values that are used on the element
|
||||
* (which is referenced by the lNodeIndex)
|
||||
*/
|
||||
function discoverDirectiveIndices(lViewData: LViewData, lNodeIndex: number): number[]|null {
|
||||
const directivesAcrossView = lViewData[DIRECTIVES];
|
||||
const lNode = getLNodeFromViewData(lViewData, lNodeIndex);
|
||||
if (lNode && directivesAcrossView && directivesAcrossView.length) {
|
||||
// this check for tNode is to determine if the calue is a LEmementNode instance
|
||||
const directiveIndexStart = getDirectiveStartIndex(lNode);
|
||||
const directiveIndexEnd = getDirectiveEndIndex(lNode, directiveIndexStart);
|
||||
const directiveIndices: number[] = [];
|
||||
for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
|
||||
// special case since the instance of the component (if it exists)
|
||||
// is stored in the directives array.
|
||||
if (i > directiveIndexStart ||
|
||||
!isComponentInstance(directivesAcrossView[directiveIndexStart])) {
|
||||
directiveIndices.push(i);
|
||||
}
|
||||
}
|
||||
return directiveIndices.length ? directiveIndices : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function discoverDirectives(lViewData: LViewData, directiveIndices: number[]): number[]|null {
|
||||
const directives: any[] = [];
|
||||
const directiveInstances = lViewData[DIRECTIVES];
|
||||
if (directiveInstances) {
|
||||
for (let i = 0; i < directiveIndices.length; i++) {
|
||||
const directiveIndex = directiveIndices[i];
|
||||
const directive = directiveInstances[directiveIndex];
|
||||
directives.push(directive);
|
||||
}
|
||||
}
|
||||
return directives;
|
||||
}
|
||||
|
||||
function getDirectiveStartIndex(lNode: LElementNode): number {
|
||||
// the tNode instances store a flag value which then has a
|
||||
// pointer which tells the starting index of where all the
|
||||
// active directives are in the master directive array
|
||||
return lNode.tNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
|
||||
}
|
||||
|
||||
function getDirectiveEndIndex(lNode: LElementNode, startIndex: number): number {
|
||||
// The end value is also apart of the same flag
|
||||
// (see `TNodeFlags` to see how the flag bit shifting
|
||||
// values are used).
|
||||
const count = lNode.tNode.flags & TNodeFlags.DirectiveCountMask;
|
||||
return count ? (startIndex + count) : -1;
|
||||
}
|
|
@ -11,8 +11,9 @@ import {Renderer2, RendererType2} from '../render/api';
|
|||
import {DebugContext} from '../view';
|
||||
import {DebugRenderer2, DebugRendererFactory2} from '../view/services';
|
||||
|
||||
import {getLElementNode} from './context_discovery';
|
||||
import * as di from './di';
|
||||
import {NG_HOST_SYMBOL, _getViewData} from './instructions';
|
||||
import {_getViewData} from './instructions';
|
||||
import {LElementNode} from './interfaces/node';
|
||||
import {CONTEXT, DIRECTIVES, LViewData, TVIEW} from './interfaces/view';
|
||||
|
||||
|
@ -85,7 +86,7 @@ class Render3DebugContext implements DebugContext {
|
|||
const currentNode = this.view[this.nodeIndex];
|
||||
for (let dirIndex = 0; dirIndex < directives.length; dirIndex++) {
|
||||
const directive = directives[dirIndex];
|
||||
if (directive[NG_HOST_SYMBOL] === currentNode) {
|
||||
if (getLElementNode(directive) === currentNode) {
|
||||
matchedDirectives.push(directive.constructor);
|
||||
}
|
||||
}
|
||||
|
@ -112,4 +113,4 @@ class Render3DebugContext implements DebugContext {
|
|||
|
||||
// TODO(vicb): check previous implementation
|
||||
logError(console: Console, ...values: any[]): void { console.error(...values); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
import {RElement} from './interfaces/renderer';
|
||||
import {HEADER_OFFSET, LViewData} from './interfaces/view';
|
||||
import {StylingIndex} from './styling';
|
||||
|
||||
export const MONKEY_PATCH_KEY_NAME = '__ng_data__';
|
||||
|
||||
/** The internal element context which is specific to a given DOM node */
|
||||
export interface ElementContext {
|
||||
/** The component\'s view data */
|
||||
lViewData: LViewData;
|
||||
|
||||
/** The index of the element within the view data array */
|
||||
index: number;
|
||||
|
||||
/** The instance of the DOM node */
|
||||
native: RElement;
|
||||
}
|
||||
|
||||
/** Returns the matching `ElementContext` data for a given DOM node.
|
||||
*
|
||||
* This function will examine the provided DOM element's monkey-patched property to figure out the
|
||||
* associated index and view data (`LViewData`).
|
||||
*
|
||||
* If the monkey-patched value is the `LViewData` instance then the element context for that
|
||||
* element will be created and the monkey-patch reference will be updated. Therefore when this
|
||||
* function is called it may mutate the provided element\'s monkey-patch value.
|
||||
*
|
||||
* If the monkey-patch value is not detected then the code will walk up the DOM until an element
|
||||
* is found which contains a monkey-patch reference. When that occurs then the provided element
|
||||
* will be updated with a new context (which is then returned).
|
||||
*/
|
||||
export function getElementContext(element: RElement): ElementContext|null {
|
||||
let context = (element as any)[MONKEY_PATCH_KEY_NAME] as ElementContext | LViewData | null;
|
||||
if (context) {
|
||||
if (Array.isArray(context)) {
|
||||
const lViewData = context as LViewData;
|
||||
const index = findMatchingElement(element, lViewData);
|
||||
context = {index, native: element, lViewData};
|
||||
attachLViewDataToNode(element, context);
|
||||
}
|
||||
} else {
|
||||
let parent = element as any;
|
||||
while (parent = parent.parentNode) {
|
||||
const parentContext =
|
||||
(parent as any)[MONKEY_PATCH_KEY_NAME] as ElementContext | LViewData | null;
|
||||
if (parentContext) {
|
||||
const lViewData =
|
||||
Array.isArray(parentContext) ? (parentContext as LViewData) : parentContext.lViewData;
|
||||
const index = findMatchingElement(element, lViewData);
|
||||
if (index >= 0) {
|
||||
context = {index, native: element, lViewData};
|
||||
attachLViewDataToNode(element, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (context as ElementContext) || null;
|
||||
}
|
||||
|
||||
/** Locates the element within the given LViewData and returns the matching index */
|
||||
function findMatchingElement(element: RElement, lViewData: LViewData): number {
|
||||
for (let i = HEADER_OFFSET; i < lViewData.length; i++) {
|
||||
let result = lViewData[i];
|
||||
if (result) {
|
||||
// special case for styling since when [class] and [style] bindings
|
||||
// are used they will wrap the element into a StylingContext array
|
||||
if (Array.isArray(result)) {
|
||||
result = result[StylingIndex.ElementPosition];
|
||||
}
|
||||
if (result.native === element) return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Assigns the given data to a DOM element using monkey-patching */
|
||||
export function attachLViewDataToNode(node: any, data: LViewData | ElementContext) {
|
||||
node[MONKEY_PATCH_KEY_NAME] = data;
|
||||
}
|
|
@ -13,7 +13,7 @@ import {Sanitizer} from '../sanitization/security';
|
|||
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||
|
||||
import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotEqual} from './assert';
|
||||
import {attachLViewDataToNode} from './element_discovery';
|
||||
import {attachPatchData, getLElementFromComponent, getLElementFromRootComponent} from './context_discovery';
|
||||
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
|
||||
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
|
||||
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
|
||||
|
@ -31,12 +31,6 @@ import {StylingContext, allocStylingContext, createStylingContextTemplate, rende
|
|||
import {assertDataInRangeInternal, isDifferent, loadElementInternal, loadInternal, stringify} from './util';
|
||||
import {ViewRef} from './view_ref';
|
||||
|
||||
/**
|
||||
* Directive (D) sets a property on all component instances using this constant as a key and the
|
||||
* component's host node (LElement) as the value. This is used in methods like detectChanges to
|
||||
* facilitate jumping from an instance to the host node.
|
||||
*/
|
||||
export const NG_HOST_SYMBOL = '__ngHostLNode__';
|
||||
|
||||
/**
|
||||
* A permanent marker promise which signifies that the current CD tree is
|
||||
|
@ -471,7 +465,11 @@ export function createLNode(
|
|||
if (previousTNode.dynamicContainerNode) previousTNode.dynamicContainerNode.next = tNode;
|
||||
}
|
||||
}
|
||||
|
||||
node.tNode = tData[adjustedIndex] as TNode;
|
||||
if (!tView.firstChild && type === TNodeType.Element) {
|
||||
tView.firstChild = node.tNode;
|
||||
}
|
||||
|
||||
// Now link ourselves into the tree.
|
||||
if (isParent) {
|
||||
|
@ -806,7 +804,7 @@ export function elementStart(
|
|||
// monkey-patched with the component view data so that the element can be inspected
|
||||
// later on using any element discovery utility methods (see `element_discovery.ts`)
|
||||
if (elementDepthCount === 0) {
|
||||
attachLViewDataToNode(native, viewData);
|
||||
attachPatchData(native, viewData);
|
||||
}
|
||||
elementDepthCount++;
|
||||
}
|
||||
|
@ -1096,7 +1094,8 @@ export function createTView(
|
|||
components: null,
|
||||
directiveRegistry: typeof directives === 'function' ? directives() : directives,
|
||||
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
|
||||
currentMatches: null
|
||||
currentMatches: null,
|
||||
firstChild: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1760,8 +1759,7 @@ export function baseDirectiveCreate<T>(
|
|||
'directives should be created before any bindings');
|
||||
ngDevMode && assertPreviousIsParent();
|
||||
|
||||
Object.defineProperty(
|
||||
directive, NG_HOST_SYMBOL, {enumerable: false, value: previousOrParentNode});
|
||||
attachPatchData(directive, viewData);
|
||||
|
||||
if (directives == null) viewData[DIRECTIVES] = directives = [];
|
||||
|
||||
|
@ -2416,7 +2414,7 @@ export function tick<T>(component: T): void {
|
|||
function tickRootContext(rootContext: RootContext) {
|
||||
for (let i = 0; i < rootContext.components.length; i++) {
|
||||
const rootComponent = rootContext.components[i];
|
||||
const hostNode = _getComponentHostLElementNode(rootComponent);
|
||||
const hostNode = _getComponentHostLElementNode(rootComponent, true);
|
||||
|
||||
ngDevMode && assertDefined(hostNode.data, 'Component host node should be attached to an LView');
|
||||
renderComponentOrTemplate(hostNode, getRootView(rootComponent), rootComponent);
|
||||
|
@ -2865,9 +2863,11 @@ function assertDataNext(index: number, arr?: any[]) {
|
|||
arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`);
|
||||
}
|
||||
|
||||
export function _getComponentHostLElementNode<T>(component: T): LElementNode {
|
||||
export function _getComponentHostLElementNode<T>(
|
||||
component: T, isRootComponent?: boolean): LElementNode {
|
||||
ngDevMode && assertDefined(component, 'expecting component got null');
|
||||
const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
|
||||
const lElementNode = isRootComponent ? getLElementFromRootComponent(component) ! :
|
||||
getLElementFromComponent(component) !;
|
||||
ngDevMode && assertDefined(component, 'object is not a component');
|
||||
return lElementNode;
|
||||
}
|
||||
|
|
|
@ -314,6 +314,11 @@ export interface TView {
|
|||
*/
|
||||
childIndex: number;
|
||||
|
||||
/**
|
||||
* A reference to the first child node located in the view.
|
||||
*/
|
||||
firstChild: TNode|null;
|
||||
|
||||
/**
|
||||
* Selector matches for a node are temporarily cached on the TView so the
|
||||
* DI system can eagerly instantiate directives on the same node if they are
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {assertDefined} from './assert';
|
||||
import {attachLViewDataToNode} from './element_discovery';
|
||||
import {attachPatchData} from './context_discovery';
|
||||
import {callHooks} from './hooks';
|
||||
import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
|
||||
import {LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
|
||||
|
@ -701,7 +701,7 @@ export function appendProjectedNode(
|
|||
// the projected contents are processed while in the shadow view (which is the currentView)
|
||||
// therefore we need to extract the view where the host element lives since it's the
|
||||
// logical container of the content projected views
|
||||
attachLViewDataToNode(node.native, parentView);
|
||||
attachPatchData(node.native, parentView);
|
||||
|
||||
if (node.tNode.type === TNodeType.Container) {
|
||||
// The node we are adding is a container and we are adding it to an element which
|
||||
|
|
|
@ -44,15 +44,15 @@
|
|||
{
|
||||
"name": "INJECTOR$1"
|
||||
},
|
||||
{
|
||||
"name": "MONKEY_PATCH_KEY_NAME"
|
||||
},
|
||||
{
|
||||
"name": "NEXT"
|
||||
},
|
||||
{
|
||||
"name": "NG_ELEMENT_ID"
|
||||
},
|
||||
{
|
||||
"name": "NG_HOST_SYMBOL"
|
||||
},
|
||||
{
|
||||
"name": "NG_PROJECT_AS_ATTR_NAME"
|
||||
},
|
||||
|
@ -98,6 +98,9 @@
|
|||
{
|
||||
"name": "appendChild"
|
||||
},
|
||||
{
|
||||
"name": "attachPatchData"
|
||||
},
|
||||
{
|
||||
"name": "baseDirectiveCreate"
|
||||
},
|
||||
|
@ -125,6 +128,9 @@
|
|||
{
|
||||
"name": "componentRefresh"
|
||||
},
|
||||
{
|
||||
"name": "createLContext"
|
||||
},
|
||||
{
|
||||
"name": "createLNode"
|
||||
},
|
||||
|
@ -185,12 +191,21 @@
|
|||
{
|
||||
"name": "extractPipeDef"
|
||||
},
|
||||
{
|
||||
"name": "findViaComponent"
|
||||
},
|
||||
{
|
||||
"name": "firstTemplatePass"
|
||||
},
|
||||
{
|
||||
"name": "getChildLNode"
|
||||
},
|
||||
{
|
||||
"name": "getLElementFromComponent"
|
||||
},
|
||||
{
|
||||
"name": "getLElementFromRootComponent"
|
||||
},
|
||||
{
|
||||
"name": "getLViewChild"
|
||||
},
|
||||
|
@ -251,6 +266,9 @@
|
|||
{
|
||||
"name": "readElementValue"
|
||||
},
|
||||
{
|
||||
"name": "readPatchedData"
|
||||
},
|
||||
{
|
||||
"name": "refreshChildComponents"
|
||||
},
|
||||
|
|
|
@ -83,9 +83,6 @@
|
|||
{
|
||||
"name": "NG_ELEMENT_ID"
|
||||
},
|
||||
{
|
||||
"name": "NG_HOST_SYMBOL"
|
||||
},
|
||||
{
|
||||
"name": "NG_PROJECT_AS_ATTR_NAME"
|
||||
},
|
||||
|
@ -333,7 +330,7 @@
|
|||
"name": "assertTemplate"
|
||||
},
|
||||
{
|
||||
"name": "attachLViewDataToNode"
|
||||
"name": "attachPatchData"
|
||||
},
|
||||
{
|
||||
"name": "baseDirectiveCreate"
|
||||
|
@ -401,6 +398,9 @@
|
|||
{
|
||||
"name": "createLContainer"
|
||||
},
|
||||
{
|
||||
"name": "createLContext"
|
||||
},
|
||||
{
|
||||
"name": "createLNode"
|
||||
},
|
||||
|
@ -530,6 +530,9 @@
|
|||
{
|
||||
"name": "findDirectiveMatches"
|
||||
},
|
||||
{
|
||||
"name": "findViaComponent"
|
||||
},
|
||||
{
|
||||
"name": "firstTemplatePass"
|
||||
},
|
||||
|
@ -560,6 +563,12 @@
|
|||
{
|
||||
"name": "getInitialValue"
|
||||
},
|
||||
{
|
||||
"name": "getLElementFromComponent"
|
||||
},
|
||||
{
|
||||
"name": "getLElementFromRootComponent"
|
||||
},
|
||||
{
|
||||
"name": "getLViewChild"
|
||||
},
|
||||
|
@ -812,6 +821,9 @@
|
|||
{
|
||||
"name": "readElementValue"
|
||||
},
|
||||
{
|
||||
"name": "readPatchedData"
|
||||
},
|
||||
{
|
||||
"name": "reference"
|
||||
},
|
||||
|
|
|
@ -464,9 +464,6 @@
|
|||
{
|
||||
"name": "NG_ELEMENT_ID"
|
||||
},
|
||||
{
|
||||
"name": "NG_HOST_SYMBOL"
|
||||
},
|
||||
{
|
||||
"name": "NG_PROJECT_AS_ATTR_NAME"
|
||||
},
|
||||
|
@ -1161,7 +1158,7 @@
|
|||
"name": "assertTemplate"
|
||||
},
|
||||
{
|
||||
"name": "attachLViewDataToNode"
|
||||
"name": "attachPatchData"
|
||||
},
|
||||
{
|
||||
"name": "baseDirectiveCreate"
|
||||
|
@ -1274,6 +1271,9 @@
|
|||
{
|
||||
"name": "createLContainer"
|
||||
},
|
||||
{
|
||||
"name": "createLContext"
|
||||
},
|
||||
{
|
||||
"name": "createLNode"
|
||||
},
|
||||
|
@ -1475,6 +1475,9 @@
|
|||
{
|
||||
"name": "findLocaleData"
|
||||
},
|
||||
{
|
||||
"name": "findViaComponent"
|
||||
},
|
||||
{
|
||||
"name": "firstTemplatePass"
|
||||
},
|
||||
|
@ -1589,6 +1592,12 @@
|
|||
{
|
||||
"name": "getInitialValue"
|
||||
},
|
||||
{
|
||||
"name": "getLElementFromComponent"
|
||||
},
|
||||
{
|
||||
"name": "getLElementFromRootComponent"
|
||||
},
|
||||
{
|
||||
"name": "getLViewChild"
|
||||
},
|
||||
|
@ -2129,6 +2138,9 @@
|
|||
{
|
||||
"name": "readElementValue"
|
||||
},
|
||||
{
|
||||
"name": "readPatchedData"
|
||||
},
|
||||
{
|
||||
"name": "recursivelyProcessProviders"
|
||||
},
|
||||
|
|
|
@ -15,13 +15,13 @@ import {AttributeMarker, defineComponent, defineDirective, injectElementRef, inj
|
|||
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, listener, load, loadDirective, projection, projectionDef, text, textBinding, template} from '../../src/render3/instructions';
|
||||
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
||||
import {HEADER_OFFSET} from '../../src/render3/interfaces/view';
|
||||
import {HEADER_OFFSET, CONTEXT, DIRECTIVES} from '../../src/render3/interfaces/view';
|
||||
import {sanitizeUrl} from '../../src/sanitization/sanitization';
|
||||
import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
|
||||
|
||||
import {NgIf} from './common_with_def';
|
||||
import {ComponentFixture, TemplateFixture, containerEl, createComponent, renderToHtml} from './render_util';
|
||||
import {MONKEY_PATCH_KEY_NAME, getElementContext} from '../../src/render3/element_discovery';
|
||||
import {MONKEY_PATCH_KEY_NAME, getContext} from '../../src/render3/context_discovery';
|
||||
import {StylingIndex} from '../../src/render3/styling';
|
||||
|
||||
describe('render3 integration test', () => {
|
||||
|
@ -1595,16 +1595,16 @@ describe('render3 integration test', () => {
|
|||
fixture.update();
|
||||
|
||||
const section = fixture.hostElement.querySelector('section') !;
|
||||
const sectionContext = getElementContext(section) !;
|
||||
const sectionLView = sectionContext.lViewData;
|
||||
expect(sectionContext.index).toEqual(HEADER_OFFSET);
|
||||
const sectionContext = getContext(section) !;
|
||||
const sectionLView = sectionContext.lViewData !;
|
||||
expect(sectionContext.lNodeIndex).toEqual(HEADER_OFFSET);
|
||||
expect(sectionLView.length).toBeGreaterThan(HEADER_OFFSET);
|
||||
expect(sectionContext.native).toBe(section);
|
||||
|
||||
const div = fixture.hostElement.querySelector('div') !;
|
||||
const divContext = getElementContext(div) !;
|
||||
const divLView = divContext.lViewData;
|
||||
expect(divContext.index).toEqual(HEADER_OFFSET + 1);
|
||||
const divContext = getContext(div) !;
|
||||
const divLView = divContext.lViewData !;
|
||||
expect(divContext.lNodeIndex).toEqual(HEADER_OFFSET + 1);
|
||||
expect(divLView.length).toBeGreaterThan(HEADER_OFFSET);
|
||||
expect(divContext.native).toBe(div);
|
||||
|
||||
|
@ -1634,7 +1634,7 @@ describe('render3 integration test', () => {
|
|||
const result1 = section[MONKEY_PATCH_KEY_NAME];
|
||||
expect(Array.isArray(result1)).toBeTruthy();
|
||||
|
||||
const context = getElementContext(section) !;
|
||||
const context = getContext(section) !;
|
||||
const result2 = section[MONKEY_PATCH_KEY_NAME];
|
||||
expect(Array.isArray(result2)).toBeFalsy();
|
||||
|
||||
|
@ -1670,7 +1670,7 @@ describe('render3 integration test', () => {
|
|||
const p = fixture.hostElement.querySelector('p') !as any;
|
||||
expect(p[MONKEY_PATCH_KEY_NAME]).toBeFalsy();
|
||||
|
||||
const pContext = getElementContext(p) !;
|
||||
const pContext = getContext(p) !;
|
||||
expect(pContext.native).toBe(p);
|
||||
expect(p[MONKEY_PATCH_KEY_NAME]).toBe(pContext);
|
||||
});
|
||||
|
@ -1708,7 +1708,7 @@ describe('render3 integration test', () => {
|
|||
expect(Array.isArray(elementResult)).toBeTruthy();
|
||||
expect(elementResult[StylingIndex.ElementPosition].native).toBe(section);
|
||||
|
||||
const context = getElementContext(section) !;
|
||||
const context = getContext(section) !;
|
||||
const result2 = section[MONKEY_PATCH_KEY_NAME];
|
||||
expect(Array.isArray(result2)).toBeFalsy();
|
||||
|
||||
|
@ -1802,9 +1802,9 @@ describe('render3 integration test', () => {
|
|||
expect(pText[MONKEY_PATCH_KEY_NAME]).toBeFalsy();
|
||||
expect(projectedTextNode[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
|
||||
|
||||
const parentContext = getElementContext(section) !;
|
||||
const shadowContext = getElementContext(header) !;
|
||||
const projectedContext = getElementContext(p) !;
|
||||
const parentContext = getContext(section) !;
|
||||
const shadowContext = getContext(header) !;
|
||||
const projectedContext = getContext(p) !;
|
||||
|
||||
const parentComponentData = parentContext.lViewData;
|
||||
const shadowComponentData = shadowContext.lViewData;
|
||||
|
@ -1817,12 +1817,12 @@ describe('render3 integration test', () => {
|
|||
it('should return `null` when an element context is retrieved that isn\'t situated in Angular',
|
||||
() => {
|
||||
const elm1 = document.createElement('div');
|
||||
const context1 = getElementContext(elm1);
|
||||
const context1 = getContext(elm1);
|
||||
expect(context1).toBeFalsy();
|
||||
|
||||
const elm2 = document.createElement('div');
|
||||
document.body.appendChild(elm2);
|
||||
const context2 = getElementContext(elm2);
|
||||
const context2 = getContext(elm2);
|
||||
expect(context2).toBeFalsy();
|
||||
});
|
||||
|
||||
|
@ -1850,9 +1850,291 @@ describe('render3 integration test', () => {
|
|||
const manuallyCreatedElement = document.createElement('div');
|
||||
section.appendChild(manuallyCreatedElement);
|
||||
|
||||
const context = getElementContext(manuallyCreatedElement);
|
||||
const context = getContext(manuallyCreatedElement);
|
||||
expect(context).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should by default monkey-patch the bootstrap component with context details', () => {
|
||||
class StructuredComp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: StructuredComp,
|
||||
selectors: [['structured-comp']],
|
||||
factory: () => new StructuredComp(),
|
||||
consts: 0,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: StructuredComp) => {}
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(StructuredComp);
|
||||
fixture.update();
|
||||
|
||||
const hostElm = fixture.hostElement;
|
||||
const component = fixture.component;
|
||||
|
||||
const componentContext = (component as any)[MONKEY_PATCH_KEY_NAME];
|
||||
expect(Array.isArray(componentContext)).toBeFalsy();
|
||||
|
||||
const hostContext = (hostElm as any)[MONKEY_PATCH_KEY_NAME];
|
||||
expect(hostContext).toBe(componentContext);
|
||||
|
||||
const context1 = getContext(hostElm) !;
|
||||
expect(context1).toBe(hostContext);
|
||||
expect(context1.native).toEqual(hostElm);
|
||||
|
||||
const context2 = getContext(component) !;
|
||||
expect(context2).toBe(context1);
|
||||
expect(context2).toBe(hostContext);
|
||||
expect(context2.native).toEqual(hostElm);
|
||||
});
|
||||
|
||||
it('should by default monkey-patch the directives with LViewData so that they can be examined',
|
||||
() => {
|
||||
let myDir1Instance: MyDir1|null = null;
|
||||
let myDir2Instance: MyDir2|null = null;
|
||||
let myDir3Instance: MyDir2|null = null;
|
||||
|
||||
class MyDir1 {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: MyDir1,
|
||||
selectors: [['', 'my-dir-1', '']],
|
||||
factory: () => myDir1Instance = new MyDir1()
|
||||
});
|
||||
}
|
||||
|
||||
class MyDir2 {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: MyDir2,
|
||||
selectors: [['', 'my-dir-2', '']],
|
||||
factory: () => myDir2Instance = new MyDir2()
|
||||
});
|
||||
}
|
||||
|
||||
class MyDir3 {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: MyDir3,
|
||||
selectors: [['', 'my-dir-3', '']],
|
||||
factory: () => myDir3Instance = new MyDir2()
|
||||
});
|
||||
}
|
||||
|
||||
class StructuredComp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: StructuredComp,
|
||||
selectors: [['structured-comp']],
|
||||
directives: [MyDir1, MyDir2, MyDir3],
|
||||
factory: () => new StructuredComp(),
|
||||
consts: 2,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: StructuredComp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', ['my-dir-1', '', 'my-dir-2', '']);
|
||||
element(1, 'div', ['my-dir-3']);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(StructuredComp);
|
||||
fixture.update();
|
||||
|
||||
const hostElm = fixture.hostElement;
|
||||
const div1 = hostElm.querySelector('div:first-child') !as any;
|
||||
const div2 = hostElm.querySelector('div:last-child') !as any;
|
||||
const context = getContext(hostElm) !;
|
||||
const elementNode = context.lViewData[context.lNodeIndex];
|
||||
const elmData = elementNode.data !;
|
||||
const dirs = elmData[DIRECTIVES];
|
||||
|
||||
expect(dirs).toContain(myDir1Instance);
|
||||
expect(dirs).toContain(myDir2Instance);
|
||||
expect(dirs).toContain(myDir3Instance);
|
||||
|
||||
expect(Array.isArray((myDir1Instance as any)[MONKEY_PATCH_KEY_NAME])).toBeTruthy();
|
||||
expect(Array.isArray((myDir2Instance as any)[MONKEY_PATCH_KEY_NAME])).toBeTruthy();
|
||||
expect(Array.isArray((myDir3Instance as any)[MONKEY_PATCH_KEY_NAME])).toBeTruthy();
|
||||
|
||||
const d1Context = getContext(myDir1Instance) !;
|
||||
const d2Context = getContext(myDir2Instance) !;
|
||||
const d3Context = getContext(myDir3Instance) !;
|
||||
|
||||
expect(d1Context.lViewData).toEqual(elmData);
|
||||
expect(d2Context.lViewData).toEqual(elmData);
|
||||
expect(d3Context.lViewData).toEqual(elmData);
|
||||
|
||||
expect((myDir1Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(d1Context);
|
||||
expect((myDir2Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(d2Context);
|
||||
expect((myDir3Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(d3Context);
|
||||
|
||||
expect(d1Context.lNodeIndex).toEqual(HEADER_OFFSET);
|
||||
expect(d1Context.native).toBe(div1);
|
||||
expect(d1Context.directives as any[]).toEqual([myDir1Instance, myDir2Instance]);
|
||||
|
||||
expect(d2Context.lNodeIndex).toEqual(HEADER_OFFSET);
|
||||
expect(d2Context.native).toBe(div1);
|
||||
expect(d2Context.directives as any[]).toEqual([myDir1Instance, myDir2Instance]);
|
||||
|
||||
expect(d3Context.lNodeIndex).toEqual(HEADER_OFFSET + 1);
|
||||
expect(d3Context.native).toBe(div2);
|
||||
expect(d3Context.directives as any[]).toEqual([myDir3Instance]);
|
||||
});
|
||||
|
||||
it('should monkey-patch the exact same context instance of the DOM node, component and any directives on the same element',
|
||||
() => {
|
||||
let myDir1Instance: MyDir1|null = null;
|
||||
let myDir2Instance: MyDir2|null = null;
|
||||
let childComponentInstance: ChildComp|null = null;
|
||||
|
||||
class MyDir1 {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: MyDir1,
|
||||
selectors: [['', 'my-dir-1', '']],
|
||||
factory: () => myDir1Instance = new MyDir1()
|
||||
});
|
||||
}
|
||||
|
||||
class MyDir2 {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: MyDir2,
|
||||
selectors: [['', 'my-dir-2', '']],
|
||||
factory: () => myDir2Instance = new MyDir2()
|
||||
});
|
||||
}
|
||||
|
||||
class ChildComp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: ChildComp,
|
||||
selectors: [['child-comp']],
|
||||
factory: () => childComponentInstance = new ChildComp(),
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: ChildComp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class ParentComp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: ParentComp,
|
||||
selectors: [['parent-comp']],
|
||||
directives: [ChildComp, MyDir1, MyDir2],
|
||||
factory: () => new ParentComp(),
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: ParentComp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'child-comp', ['my-dir-1', '', 'my-dir-2', '']);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(ParentComp);
|
||||
fixture.update();
|
||||
|
||||
const childCompHostElm = fixture.hostElement.querySelector('child-comp') !as any;
|
||||
|
||||
const lViewData = childCompHostElm[MONKEY_PATCH_KEY_NAME];
|
||||
expect(Array.isArray(lViewData)).toBeTruthy();
|
||||
expect((myDir1Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(lViewData);
|
||||
expect((myDir2Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(lViewData);
|
||||
expect((childComponentInstance as any)[MONKEY_PATCH_KEY_NAME]).toBe(lViewData);
|
||||
|
||||
const childNodeContext = getContext(childCompHostElm) !;
|
||||
expect(childNodeContext.component).toBeFalsy();
|
||||
expect(childNodeContext.directives).toBeFalsy();
|
||||
assertMonkeyPatchValueIsLViewData(myDir1Instance);
|
||||
assertMonkeyPatchValueIsLViewData(myDir2Instance);
|
||||
assertMonkeyPatchValueIsLViewData(childComponentInstance);
|
||||
|
||||
expect(getContext(myDir1Instance)).toBe(childNodeContext);
|
||||
expect(childNodeContext.component).toBeFalsy();
|
||||
expect(childNodeContext.directives !.length).toEqual(2);
|
||||
assertMonkeyPatchValueIsLViewData(myDir1Instance, false);
|
||||
assertMonkeyPatchValueIsLViewData(myDir2Instance, false);
|
||||
assertMonkeyPatchValueIsLViewData(childComponentInstance);
|
||||
|
||||
expect(getContext(myDir2Instance)).toBe(childNodeContext);
|
||||
expect(childNodeContext.component).toBeFalsy();
|
||||
expect(childNodeContext.directives !.length).toEqual(2);
|
||||
assertMonkeyPatchValueIsLViewData(myDir1Instance, false);
|
||||
assertMonkeyPatchValueIsLViewData(myDir2Instance, false);
|
||||
assertMonkeyPatchValueIsLViewData(childComponentInstance);
|
||||
|
||||
expect(getContext(childComponentInstance)).toBe(childNodeContext);
|
||||
expect(childNodeContext.component).toBeTruthy();
|
||||
expect(childNodeContext.directives !.length).toEqual(2);
|
||||
assertMonkeyPatchValueIsLViewData(myDir1Instance, false);
|
||||
assertMonkeyPatchValueIsLViewData(myDir2Instance, false);
|
||||
assertMonkeyPatchValueIsLViewData(childComponentInstance, false);
|
||||
|
||||
function assertMonkeyPatchValueIsLViewData(value: any, yesOrNo = true) {
|
||||
expect(Array.isArray((value as any)[MONKEY_PATCH_KEY_NAME])).toBe(yesOrNo);
|
||||
}
|
||||
});
|
||||
|
||||
it('should monkey-patch sub components with the view data and then replace them with the context result once a lookup occurs',
|
||||
() => {
|
||||
class ChildComp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: ChildComp,
|
||||
selectors: [['child-comp']],
|
||||
factory: () => new ChildComp(),
|
||||
consts: 3,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: ChildComp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div');
|
||||
element(1, 'div');
|
||||
element(2, 'div');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class ParentComp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: ParentComp,
|
||||
selectors: [['parent-comp']],
|
||||
directives: [ChildComp],
|
||||
factory: () => new ParentComp(),
|
||||
consts: 2,
|
||||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: ParentComp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'section');
|
||||
elementStart(1, 'child-comp');
|
||||
elementEnd();
|
||||
elementEnd();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(ParentComp);
|
||||
fixture.update();
|
||||
|
||||
const host = fixture.hostElement;
|
||||
const child = host.querySelector('child-comp') as any;
|
||||
expect(child[MONKEY_PATCH_KEY_NAME]).toBeFalsy();
|
||||
|
||||
const context = getContext(child) !;
|
||||
expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
|
||||
|
||||
const componentData = context.lViewData[context.lNodeIndex].data;
|
||||
const component = componentData[CONTEXT];
|
||||
expect(component instanceof ChildComp).toBeTruthy();
|
||||
expect(component[MONKEY_PATCH_KEY_NAME]).toBe(context.lViewData);
|
||||
|
||||
const componentContext = getContext(component) !;
|
||||
expect(component[MONKEY_PATCH_KEY_NAME]).toBe(componentContext);
|
||||
expect(componentContext.lNodeIndex).toEqual(context.lNodeIndex);
|
||||
expect(componentContext.native).toEqual(context.native);
|
||||
expect(componentContext.lViewData).toEqual(context.lViewData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitization', () => {
|
||||
|
|
|
@ -10,9 +10,10 @@ import {stringifyElement} from '@angular/platform-browser/testing/src/browser_ut
|
|||
|
||||
import {Injector} from '../../src/di/injector';
|
||||
import {CreateComponentOptions} from '../../src/render3/component';
|
||||
import {getContext, isComponentInstance} from '../../src/render3/context_discovery';
|
||||
import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition';
|
||||
import {ComponentTemplate, ComponentType, DirectiveDefInternal, DirectiveType, PublicFeature, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index';
|
||||
import {NG_HOST_SYMBOL, renderTemplate} from '../../src/render3/instructions';
|
||||
import {renderTemplate} from '../../src/render3/instructions';
|
||||
import {DirectiveDefList, DirectiveTypesOrFactory, PipeDefInternal, PipeDefList, PipeTypesOrFactory} from '../../src/render3/interfaces/definition';
|
||||
import {LElementNode} from '../../src/render3/interfaces/node';
|
||||
import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
||||
|
@ -219,17 +220,24 @@ export function renderComponent<T>(type: ComponentType<T>, opts?: CreateComponen
|
|||
* @deprecated use `TemplateFixture` or `ComponentFixture`
|
||||
*/
|
||||
export function toHtml<T>(componentOrElement: T | RElement): string {
|
||||
const node = (componentOrElement as any)[NG_HOST_SYMBOL] as LElementNode;
|
||||
if (node) {
|
||||
return toHtml(node.native);
|
||||
let element: any;
|
||||
if (isComponentInstance(componentOrElement)) {
|
||||
const context = getContext(componentOrElement);
|
||||
element = context ? context.native : null;
|
||||
} else {
|
||||
return stringifyElement(componentOrElement)
|
||||
element = componentOrElement;
|
||||
}
|
||||
|
||||
if (element) {
|
||||
return stringifyElement(element)
|
||||
.replace(/^<div host="">/, '')
|
||||
.replace(/^<div fixture="mark">/, '')
|
||||
.replace(/<\/div>$/, '')
|
||||
.replace(' style=""', '')
|
||||
.replace(/<!--container-->/g, '')
|
||||
.replace(/<!--ng-container-->/g, '');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue