fix(ivy): Implement remaining methods for DebugNode (#27387)

PR Close #27387
This commit is contained in:
Miško Hevery 2018-11-28 15:54:38 -08:00 committed by Igor Minar
parent f0b0d64453
commit b2d6f43b49
34 changed files with 605 additions and 406 deletions

View File

@ -154,7 +154,7 @@ export {
} from './sanitization/bypass'; } from './sanitization/bypass';
export { export {
getContext as ɵgetContext getLContext as ɵgetLContext
} from './render3/context_discovery'; } from './render3/context_discovery';
export { export {

View File

@ -7,11 +7,13 @@
*/ */
import {Injector} from '../di'; import {Injector} from '../di';
import {DirectiveDef} from '../render3';
import {assertDomNode} from '../render3/assert'; import {assertDomNode} from '../render3/assert';
import {getComponent, getInjector, getLocalRefs, loadContext} from '../render3/discovery_utils'; import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext, loadLContextFromNode} from '../render3/discovery_utils';
import {TNode, TNodeFlags} from '../render3/interfaces/node'; import {TNode} from '../render3/interfaces/node';
import {StylingIndex} from '../render3/interfaces/styling';
import {TVIEW} from '../render3/interfaces/view'; import {TVIEW} from '../render3/interfaces/view';
import {getProp, getValue, isClassBased} from '../render3/styling/class_and_style_bindings';
import {getStylingContext} from '../render3/styling/util';
import {DebugContext} from '../view/index'; import {DebugContext} from '../view/index';
export class EventListener { export class EventListener {
@ -204,7 +206,7 @@ class DebugNode__POST_R3__ implements DebugNode {
constructor(nativeNode: Node) { this.nativeNode = nativeNode; } constructor(nativeNode: Node) { this.nativeNode = nativeNode; }
get parent(): DebugElement|null { get parent(): DebugElement|null {
const parent = this.nativeNode.parentNode as HTMLElement; const parent = this.nativeNode.parentNode as Element;
return parent ? new DebugElement__POST_R3__(parent) : null; return parent ? new DebugElement__POST_R3__(parent) : null;
} }
@ -212,46 +214,17 @@ class DebugNode__POST_R3__ implements DebugNode {
get componentInstance(): any { get componentInstance(): any {
const nativeElement = this.nativeNode; const nativeElement = this.nativeNode;
return nativeElement && getComponent(nativeElement as HTMLElement); return nativeElement && getComponent(nativeElement as Element);
}
get context(): any {
// https://angular-team.atlassian.net/browse/FW-719
throw notImplemented();
} }
get context(): any { return getContext(this.nativeNode as Element); }
get listeners(): EventListener[] { get listeners(): EventListener[] {
// TODO: add real implementation; return getListeners(this.nativeNode as Element).filter(isBrowserEvents);
// https://angular-team.atlassian.net/browse/FW-719
return [];
} }
get references(): {[key: string]: any;} { return getLocalRefs(this.nativeNode); } get references(): {[key: string]: any;} { return getLocalRefs(this.nativeNode); }
get providerTokens(): any[] { get providerTokens(): any[] { return getInjectionTokens(this.nativeNode as Element); }
// TODO move to discoverable utils
const context = loadContext(this.nativeNode as HTMLElement, false) !;
if (!context) return [];
const lView = context.lView;
const tView = lView[TVIEW];
const tNode = tView.data[context.nodeIndex] as TNode;
const providerTokens: any[] = [];
const nodeFlags = tNode.flags;
const startIndex = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
const directiveCount = nodeFlags & TNodeFlags.DirectiveCountMask;
const endIndex = startIndex + directiveCount;
for (let i = startIndex; i < endIndex; i++) {
let value = tView.data[i];
if (isDirectiveDefHack(value)) {
// The fact that we sometimes store Type and sometimes DirectiveDef in this location is a
// design flaw. We should always store same type so that we can be monomorphic. The issue
// is that for Components/Directives we store the def instead the type. The correct behavior
// is that we should always be storing injectable type in this location.
value = value.type;
}
providerTokens.push(value);
}
return providerTokens;
}
} }
class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugElement { class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugElement {
@ -264,10 +237,10 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
return this.nativeNode.nodeType == Node.ELEMENT_NODE ? this.nativeNode as Element : null; return this.nativeNode.nodeType == Node.ELEMENT_NODE ? this.nativeNode as Element : null;
} }
get name(): string { return (this.nativeElement as HTMLElement).nodeName; } get name(): string { return this.nativeElement !.nodeName; }
get properties(): {[key: string]: any;} { get properties(): {[key: string]: any;} {
const context = loadContext(this.nativeNode) !; const context = loadLContext(this.nativeNode) !;
const lView = context.lView; const lView = context.lView;
const tView = lView[TVIEW]; const tView = lView[TVIEW];
const tNode = tView.data[context.nodeIndex] as TNode; const tNode = tView.data[context.nodeIndex] as TNode;
@ -278,18 +251,77 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
} }
get attributes(): {[key: string]: string | null;} { get attributes(): {[key: string]: string | null;} {
// https://angular-team.atlassian.net/browse/FW-719 const attributes: {[key: string]: string | null;} = {};
throw notImplemented(); const element = this.nativeElement;
if (element) {
const eAttrs = element.attributes;
for (let i = 0; i < eAttrs.length; i++) {
const attr = eAttrs[i];
attributes[attr.name] = attr.value;
}
}
return attributes;
} }
get classes(): {[key: string]: boolean;} { get classes(): {[key: string]: boolean;} {
// https://angular-team.atlassian.net/browse/FW-719 const classes: {[key: string]: boolean;} = {};
throw notImplemented(); const element = this.nativeElement;
if (element) {
const lContext = loadLContextFromNode(element);
const lNode = lContext.lView[lContext.nodeIndex];
const stylingContext = getStylingContext(lContext.nodeIndex, lContext.lView);
if (stylingContext) {
for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length;
i += StylingIndex.Size) {
if (isClassBased(lNode, i)) {
const className = getProp(lNode, i);
const value = getValue(lNode, i);
if (typeof value == 'boolean') {
// we want to ignore `null` since those don't overwrite the values.
classes[className] = value;
}
}
}
} else {
// Fallback, just read DOM.
const eClasses = element.classList;
for (let i = 0; i < eClasses.length; i++) {
classes[eClasses[i]] = true;
}
}
}
return classes;
} }
get styles(): {[key: string]: string | null;} { get styles(): {[key: string]: string | null;} {
// https://angular-team.atlassian.net/browse/FW-719 const styles: {[key: string]: string | null;} = {};
throw notImplemented(); const element = this.nativeElement;
if (element) {
const lContext = loadLContextFromNode(element);
const lNode = lContext.lView[lContext.nodeIndex];
const stylingContext = getStylingContext(lContext.nodeIndex, lContext.lView);
if (stylingContext) {
for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length;
i += StylingIndex.Size) {
if (!isClassBased(lNode, i)) {
const styleName = getProp(lNode, i);
const value = getValue(lNode, i) as string | null;
if (value !== null) {
// we want to ignore `null` since those don't overwrite the values.
styles[styleName] = value;
}
}
}
} else {
// Fallback, just read DOM.
const eStyles = (element as HTMLElement).style;
for (let i = 0; i < eStyles.length; i++) {
const name = eStyles.item(i);
styles[name] = eStyles.getPropertyValue(name);
}
}
}
return styles;
} }
get childNodes(): DebugNode[] { get childNodes(): DebugNode[] {
@ -332,24 +364,14 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
} }
triggerEventHandler(eventName: string, eventObj: any): void { triggerEventHandler(eventName: string, eventObj: any): void {
// This is a hack implementation. The correct implementation would bypass the DOM and `TNode` this.listeners.forEach((listener) => {
// information to invoke the listeners directly. if (listener.name === eventName) {
// https://angular-team.atlassian.net/browse/FW-719 listener.callback(eventObj);
const event = document.createEvent('MouseEvent'); }
event.initEvent(eventName, true, true); });
(this.nativeElement as HTMLElement).dispatchEvent(event);
} }
} }
/**
* This function should not exist because it is megamorphic and only mostly correct.
*
* See call site for more info.
*/
function isDirectiveDefHack(obj: any): obj is DirectiveDef<any> {
return obj.type !== undefined && obj.template !== undefined && obj.declaredInputs !== undefined;
}
function _queryNodeChildrenR3( function _queryNodeChildrenR3(
parentNode: DebugNode, predicate: Predicate<DebugNode>, matches: DebugNode[], parentNode: DebugNode, predicate: Predicate<DebugNode>, matches: DebugNode[],
elementsOnly: boolean) { elementsOnly: boolean) {

View File

@ -17,12 +17,12 @@ import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di'; import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {publishDefaultGlobalUtils} from './global_utils'; import {publishDefaultGlobalUtils} from './global_utils';
import {queueInitHooks, queueLifecycleHooks} from './hooks'; import {queueInitHooks, queueLifecycleHooks} from './hooks';
import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions'; import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTNode, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions';
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition'; import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNodeFlags, TNodeType} from './interfaces/node'; import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player'; import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view'; import {CONTEXT, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setCurrentDirectiveDef} from './state'; import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setCurrentDirectiveDef} from './state';
import {defaultScheduler, getRootView, readPatchedLView, stringify} from './util'; import {defaultScheduler, getRootView, readPatchedLView, stringify} from './util';
@ -235,7 +235,9 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): v
const dirIndex = rootTView.data.length - 1; const dirIndex = rootTView.data.length - 1;
queueInitHooks(dirIndex, def.onInit, def.doCheck, rootTView); queueInitHooks(dirIndex, def.onInit, def.doCheck, rootTView);
queueLifecycleHooks(dirIndex << TNodeFlags.DirectiveStartingIndexShift | 1, rootTView); // TODO(misko): replace `as TNode` with createTNode call. (needs refactoring to lose dep on
// LNode).
queueLifecycleHooks(rootTView, { directiveStart: dirIndex, directiveEnd: dirIndex + 1 } as TNode);
} }
/** /**

View File

@ -36,7 +36,7 @@ import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatched
* *
* @param target Component, Directive or DOM Node. * @param target Component, Directive or DOM Node.
*/ */
export function getContext(target: any): LContext|null { export function getLContext(target: any): LContext|null {
let mpValue = readPatchedData(target); let mpValue = readPatchedData(target);
if (mpValue) { if (mpValue) {
// only when it's an array is it considered an LView instance // only when it's an array is it considered an LView instance
@ -250,8 +250,8 @@ function findViaDirective(lView: LView, directiveInstance: {}): number {
// list of directives for the instance. // list of directives for the instance.
let tNode = lView[TVIEW].firstChild; let tNode = lView[TVIEW].firstChild;
while (tNode) { while (tNode) {
const directiveIndexStart = getDirectiveStartIndex(tNode); const directiveIndexStart = tNode.directiveStart;
const directiveIndexEnd = getDirectiveEndIndex(tNode, directiveIndexStart); const directiveIndexEnd = tNode.directiveEnd;
for (let i = directiveIndexStart; i < directiveIndexEnd; i++) { for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
if (lView[i] === directiveInstance) { if (lView[i] === directiveInstance) {
return tNode.index; return tNode.index;
@ -273,16 +273,16 @@ function findViaDirective(lView: LView, directiveInstance: {}): number {
export function getDirectivesAtNodeIndex( export function getDirectivesAtNodeIndex(
nodeIndex: number, lView: LView, includeComponents: boolean): any[]|null { nodeIndex: number, lView: LView, includeComponents: boolean): any[]|null {
const tNode = lView[TVIEW].data[nodeIndex] as TNode; const tNode = lView[TVIEW].data[nodeIndex] as TNode;
let directiveStartIndex = getDirectiveStartIndex(tNode); let directiveStartIndex = tNode.directiveStart;
if (directiveStartIndex == 0) return EMPTY_ARRAY; if (directiveStartIndex == 0) return EMPTY_ARRAY;
const directiveEndIndex = getDirectiveEndIndex(tNode, directiveStartIndex); const directiveEndIndex = tNode.directiveEnd;
if (!includeComponents && tNode.flags & TNodeFlags.isComponent) directiveStartIndex++; if (!includeComponents && tNode.flags & TNodeFlags.isComponent) directiveStartIndex++;
return lView.slice(directiveStartIndex, directiveEndIndex); return lView.slice(directiveStartIndex, directiveEndIndex);
} }
export function getComponentAtNodeIndex(nodeIndex: number, lView: LView): {}|null { export function getComponentAtNodeIndex(nodeIndex: number, lView: LView): {}|null {
const tNode = lView[TVIEW].data[nodeIndex] as TNode; const tNode = lView[TVIEW].data[nodeIndex] as TNode;
let directiveStartIndex = getDirectiveStartIndex(tNode); let directiveStartIndex = tNode.directiveStart;
return tNode.flags & TNodeFlags.isComponent ? lView[directiveStartIndex] : null; return tNode.flags & TNodeFlags.isComponent ? lView[directiveStartIndex] : null;
} }
@ -305,18 +305,3 @@ export function discoverLocalRefs(lView: LView, nodeIndex: number): {[key: strin
return null; return null;
} }
function getDirectiveStartIndex(tNode: TNode): 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 tNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
}
function getDirectiveEndIndex(tNode: TNode, startIndex: number): number {
// The end value is also a part of the same flag
// (see `TNodeFlags` to see how the flag bit shifting
// values are used).
const count = tNode.flags & TNodeFlags.DirectiveCountMask;
return count ? (startIndex + count) : -1;
}

View File

@ -393,30 +393,32 @@ export function getOrCreateInjectable<T>(
const NOT_FOUND = {}; const NOT_FOUND = {};
function searchTokensOnInjector<T>( function searchTokensOnInjector<T>(
injectorIndex: number, injectorView: LView, token: Type<T>| InjectionToken<T>, injectorIndex: number, lView: LView, token: Type<T>| InjectionToken<T>,
previousTView: TView | null) { previousTView: TView | null) {
const currentTView = injectorView[TVIEW]; const currentTView = lView[TVIEW];
const tNode = currentTView.data[injectorIndex + TNODE] as TNode; const tNode = currentTView.data[injectorIndex + TNODE] as TNode;
// First, we step through providers // First, we need to determine if view providers can be accessed by the starting element.
let canAccessViewProviders = false; // There are two possibities
// We need to determine if view providers can be accessed by the starting element. const canAccessViewProviders = previousTView == null ?
// It happens in 2 cases: // 1) This is the first invocation `previousTView == null` which means that we are at the
// 1) On the initial element injector , if we are instantiating a token which can see the // `TNode` of where injector is starting to look. In such a case the only time we are allowed
// viewProviders of the component of that element. Such token are: // to look into the ViewProviders is if:
// - the component itself (but not other directives) // - we are on a component
// - viewProviders tokens of the component (but not providers tokens) // - AND the injector set `includeViewProviders` to true (implying that the token can see
// 2) Upper in the element injector tree, if the starting element is actually in the view of // ViewProviders because it is the Component or a Service which itself was declared in
// the current element. To determine this, we track the transition of view during the climb, // ViewProviders)
// and check the host node of the current view to identify component views. (isComponent(tNode) && includeViewProviders) :
if (previousTView == null && isComponent(tNode) && includeViewProviders || // 2) `previousTView != null` which means that we are now walking across the parent nodes.
previousTView != null && previousTView != currentTView && // In such a case we are only allowed to look into the ViewProviders if:
(currentTView.node == null || currentTView.node.type === TNodeType.Element)) { // - We just crossed from child View to Parent View `previousTView != currentTView`
canAccessViewProviders = true; // - AND the parent TNode is an Element.
} // This means that we just came from the Component's View and therefore are allowed to see
const injectableIdx = // into the ViewProviders.
locateDirectiveOrProvider(tNode, injectorView, token, canAccessViewProviders); (previousTView != currentTView && (tNode.type === TNodeType.Element));
const injectableIdx = locateDirectiveOrProvider(tNode, lView, token, canAccessViewProviders);
if (injectableIdx !== null) { if (injectableIdx !== null) {
return getNodeInjectable(currentTView.data, injectorView, injectableIdx, tNode as TElementNode); return getNodeInjectable(currentTView.data, lView, injectableIdx, tNode as TElementNode);
} else { } else {
return NOT_FOUND; return NOT_FOUND;
} }
@ -439,17 +441,17 @@ export function locateDirectiveOrProvider<T>(
const nodeProviderIndexes = tNode.providerIndexes; const nodeProviderIndexes = tNode.providerIndexes;
const tInjectables = tView.data; const tInjectables = tView.data;
const startInjectables = nodeProviderIndexes & TNodeProviderIndexes.ProvidersStartIndexMask; const injectablesStart = nodeProviderIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const startDirectives = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift; const directivesStart = tNode.directiveStart;
const directiveEnd = tNode.directiveEnd;
const cptViewProvidersCount = const cptViewProvidersCount =
nodeProviderIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift; nodeProviderIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
const startingIndex = const startingIndex =
canAccessViewProviders ? startInjectables : startInjectables + cptViewProvidersCount; canAccessViewProviders ? injectablesStart : injectablesStart + cptViewProvidersCount;
const directiveCount = nodeFlags & TNodeFlags.DirectiveCountMask; for (let i = startingIndex; i < directiveEnd; i++) {
for (let i = startingIndex; i < startDirectives + directiveCount; i++) {
const providerTokenOrDef = tInjectables[i] as InjectionToken<any>| Type<any>| DirectiveDef<any>; const providerTokenOrDef = tInjectables[i] as InjectionToken<any>| Type<any>| DirectiveDef<any>;
if (i < startDirectives && token === providerTokenOrDef || if (i < directivesStart && token === providerTokenOrDef ||
i >= startDirectives && (providerTokenOrDef as DirectiveDef<any>).type === token) { i >= directivesStart && (providerTokenOrDef as DirectiveDef<any>).type === token) {
return i; return i;
} }
} }

View File

@ -75,12 +75,11 @@ function resolveProvider(
let token: any = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide); let token: any = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide);
let providerFactory: () => any = providerToFactory(provider); let providerFactory: () => any = providerToFactory(provider);
const previousOrParentTNode = getPreviousOrParentTNode(); const tNode = getPreviousOrParentTNode();
const beginIndex = const beginIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
previousOrParentTNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask; const endIndex = tNode.directiveStart;
const endIndex = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
const cptViewProvidersCount = const cptViewProvidersCount =
previousOrParentTNode.providerIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift; tNode.providerIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
if (isTypeProvider(provider) || !provider.multi) { if (isTypeProvider(provider) || !provider.multi) {
// Single provider case: the factory is created and pushed immediately // Single provider case: the factory is created and pushed immediately
@ -91,14 +90,13 @@ function resolveProvider(
if (existingFactoryIndex == -1) { if (existingFactoryIndex == -1) {
diPublicInInjector( diPublicInInjector(
getOrCreateNodeInjectorForNode( getOrCreateNodeInjectorForNode(
previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode, tNode as TElementNode | TContainerNode | TElementContainerNode, lView),
lView),
lView, token); lView, token);
tInjectables.push(token); tInjectables.push(token);
previousOrParentTNode.flags += 1 << TNodeFlags.DirectiveStartingIndexShift; tNode.directiveStart++;
tNode.directiveEnd++;
if (isViewProvider) { if (isViewProvider) {
previousOrParentTNode.providerIndexes += tNode.providerIndexes += TNodeProviderIndexes.CptViewProvidersCountShifter;
TNodeProviderIndexes.CptViewProvidersCountShifter;
} }
lInjectablesBlueprint.push(factory); lInjectablesBlueprint.push(factory);
lView.push(factory); lView.push(factory);
@ -142,8 +140,7 @@ function resolveProvider(
// Cases 1.a and 2.a // Cases 1.a and 2.a
diPublicInInjector( diPublicInInjector(
getOrCreateNodeInjectorForNode( getOrCreateNodeInjectorForNode(
previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode, tNode as TElementNode | TContainerNode | TElementContainerNode, lView),
lView),
lView, token); lView, token);
const factory = multiFactory( const factory = multiFactory(
isViewProvider ? multiViewProvidersFactoryResolver : multiProvidersFactoryResolver, isViewProvider ? multiViewProvidersFactoryResolver : multiProvidersFactoryResolver,
@ -152,10 +149,10 @@ function resolveProvider(
lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory; lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory;
} }
tInjectables.push(token); tInjectables.push(token);
previousOrParentTNode.flags += 1 << TNodeFlags.DirectiveStartingIndexShift; tNode.directiveStart++;
tNode.directiveEnd++;
if (isViewProvider) { if (isViewProvider) {
previousOrParentTNode.providerIndexes += tNode.providerIndexes += TNodeProviderIndexes.CptViewProvidersCountShifter;
TNodeProviderIndexes.CptViewProvidersCountShifter;
} }
lInjectablesBlueprint.push(factory); lInjectablesBlueprint.push(factory);
lView.push(factory); lView.push(factory);

View File

@ -8,10 +8,12 @@
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {assertDefined} from './assert'; import {assertDefined} from './assert';
import {discoverLocalRefs, getComponentAtNodeIndex, getContext, getDirectivesAtNodeIndex} from './context_discovery'; import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from './context_discovery';
import {LContext} from './interfaces/context'; import {LContext} from './interfaces/context';
import {TElementNode} from './interfaces/node'; import {DirectiveDef} from './interfaces/definition';
import {CONTEXT, FLAGS, HOST, LView, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view'; import {INJECTOR_BLOOM_PARENT_SIZE} from './interfaces/injector';
import {TElementNode, TNode, TNodeProviderIndexes} from './interfaces/node';
import {CLEANUP, CONTEXT, FLAGS, HOST, LView, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
import {readPatchedLView, stringify} from './util'; import {readPatchedLView, stringify} from './util';
import {NodeInjector} from './view_engine_compatibility'; import {NodeInjector} from './view_engine_compatibility';
@ -20,7 +22,7 @@ import {NodeInjector} from './view_engine_compatibility';
* Returns the component instance associated with a given DOM host element. * Returns the component instance associated with a given DOM host element.
* Elements which don't represent components return `null`. * Elements which don't represent components return `null`.
* *
* @param element Host DOM element from which the component should be retrieved for. * @param element Host DOM element from which the component should be retrieved.
* *
* ``` * ```
* <my-app> * <my-app>
@ -37,9 +39,7 @@ import {NodeInjector} from './view_engine_compatibility';
* @publicApi * @publicApi
*/ */
export function getComponent<T = {}>(element: Element): T|null { export function getComponent<T = {}>(element: Element): T|null {
if (!(element instanceof Node)) throw new Error('Expecting instance of DOM Node'); const context = loadLContextFromNode(element);
const context = loadContext(element) !;
if (context.component === undefined) { if (context.component === undefined) {
context.component = getComponentAtNodeIndex(context.nodeIndex, context.lView); context.component = getComponentAtNodeIndex(context.nodeIndex, context.lView);
@ -48,6 +48,31 @@ export function getComponent<T = {}>(element: Element): T|null {
return context.component as T; return context.component as T;
} }
/**
* Returns the component instance associated with a given DOM host element.
* Elements which don't represent components return `null`.
*
* @param element Host DOM element from which the component should be retrieved.
*
* ```
* <my-app>
* #VIEW
* <div>
* <child-comp></child-comp>
* </div>
* </mp-app>
*
* expect(getComponent(<child-comp>) instanceof ChildComponent).toBeTruthy();
* expect(getComponent(<my-app>) instanceof MyApp).toBeTruthy();
* ```
*
* @publicApi
*/
export function getContext<T = {}>(element: Element): T|null {
const context = loadLContextFromNode(element) !;
return context.lView[CONTEXT] as T;
}
/** /**
* Returns the component instance associated with view which owns the DOM element (`null` * Returns the component instance associated with view which owns the DOM element (`null`
* otherwise). * otherwise).
@ -69,7 +94,7 @@ export function getComponent<T = {}>(element: Element): T|null {
* @publicApi * @publicApi
*/ */
export function getViewComponent<T = {}>(element: Element | {}): T|null { export function getViewComponent<T = {}>(element: Element | {}): T|null {
const context = loadContext(element) !; const context = loadLContext(element) !;
let lView: LView = context.lView; let lView: LView = context.lView;
while (lView[PARENT] && lView[HOST] === null) { while (lView[PARENT] && lView[HOST] === null) {
// As long as lView[HOST] is null we know we are part of sub-template such as `*ngIf` // As long as lView[HOST] is null we know we are part of sub-template such as `*ngIf`
@ -87,8 +112,8 @@ export function getViewComponent<T = {}>(element: Element | {}): T|null {
* *
*/ */
export function getRootContext(target: LView | {}): RootContext { export function getRootContext(target: LView | {}): RootContext {
const lView = Array.isArray(target) ? target : loadContext(target) !.lView; const lViewData = Array.isArray(target) ? target : loadLContext(target) !.lView;
const rootLView = getRootView(lView); const rootLView = getRootView(lViewData);
return rootLView[CONTEXT] as RootContext; return rootLView[CONTEXT] as RootContext;
} }
@ -113,12 +138,40 @@ export function getRootComponents(target: {}): any[] {
* @publicApi * @publicApi
*/ */
export function getInjector(target: {}): Injector { export function getInjector(target: {}): Injector {
const context = loadContext(target); const context = loadLContext(target);
const tNode = context.lView[TVIEW].data[context.nodeIndex] as TElementNode; const tNode = context.lView[TVIEW].data[context.nodeIndex] as TElementNode;
return new NodeInjector(tNode, context.lView); return new NodeInjector(tNode, context.lView);
} }
/**
* Retrieve a set of injection tokens at a given DOM node.
*
* @param element Element for which the injection tokens should be retrieved.
* @publicApi
*/
export function getInjectionTokens(element: Element): any[] {
const context = loadLContext(element, false);
if (!context) return [];
const lView = context.lView;
const tView = lView[TVIEW];
const tNode = tView.data[context.nodeIndex] as TNode;
const providerTokens: any[] = [];
const startIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const endIndex = tNode.directiveEnd;
for (let i = startIndex; i < endIndex; i++) {
let value = tView.data[i];
if (isDirectiveDefHack(value)) {
// The fact that we sometimes store Type and sometimes DirectiveDef in this location is a
// design flaw. We should always store same type so that we can be monomorphic. The issue
// is that for Components/Directives we store the def instead the type. The correct behavior
// is that we should always be storing injectable type in this location.
value = value.type;
}
providerTokens.push(value);
}
return providerTokens;
}
/** /**
* Retrieves directives associated with a given DOM host element. * Retrieves directives associated with a given DOM host element.
* *
@ -127,7 +180,7 @@ export function getInjector(target: {}): Injector {
* @publicApi * @publicApi
*/ */
export function getDirectives(target: {}): Array<{}> { export function getDirectives(target: {}): Array<{}> {
const context = loadContext(target) !; const context = loadLContext(target) !;
if (context.directives === undefined) { if (context.directives === undefined) {
context.directives = getDirectivesAtNodeIndex(context.nodeIndex, context.lView, false); context.directives = getDirectivesAtNodeIndex(context.nodeIndex, context.lView, false);
@ -141,10 +194,10 @@ export function getDirectives(target: {}): Array<{}> {
* Throws if a given target doesn't have associated LContext. * Throws if a given target doesn't have associated LContext.
* *
*/ */
export function loadContext(target: {}): LContext; export function loadLContext(target: {}): LContext;
export function loadContext(target: {}, throwOnNotFound: false): LContext|null; export function loadLContext(target: {}, throwOnNotFound: false): LContext|null;
export function loadContext(target: {}, throwOnNotFound: boolean = true): LContext|null { export function loadLContext(target: {}, throwOnNotFound: boolean = true): LContext|null {
const context = getContext(target); const context = getLContext(target);
if (!context && throwOnNotFound) { if (!context && throwOnNotFound) {
throw new Error( throw new Error(
ngDevMode ? `Unable to find context associated with ${stringify(target)}` : ngDevMode ? `Unable to find context associated with ${stringify(target)}` :
@ -185,7 +238,7 @@ export function getRootView(componentOrView: LView | {}): LView {
* @publicApi * @publicApi
*/ */
export function getLocalRefs(target: {}): {[key: string]: any} { export function getLocalRefs(target: {}): {[key: string]: any} {
const context = loadContext(target) !; const context = loadLContext(target) !;
if (context.localRefs === undefined) { if (context.localRefs === undefined) {
context.localRefs = discoverLocalRefs(context.lView, context.nodeIndex); context.localRefs = discoverLocalRefs(context.lView, context.nodeIndex);
@ -205,7 +258,7 @@ export function getLocalRefs(target: {}): {[key: string]: any} {
* @publicApi * @publicApi
*/ */
export function getHostElement<T>(directive: T): Element { export function getHostElement<T>(directive: T): Element {
return getContext(directive) !.native as never as Element; return getLContext(directive) !.native as never as Element;
} }
/** /**
@ -221,4 +274,89 @@ export function getHostElement<T>(directive: T): Element {
export function getRenderedText(component: any): string { export function getRenderedText(component: any): string {
const hostElement = getHostElement(component); const hostElement = getHostElement(component);
return hostElement.textContent || ''; return hostElement.textContent || '';
} }
export function loadLContextFromNode(node: Node): LContext {
if (!(node instanceof Node)) throw new Error('Expecting instance of DOM Node');
return loadLContext(node) !;
}
export interface Listener {
name: string;
element: Element;
callback: (value: any) => any;
useCapture: boolean|null;
}
export function isBrowserEvents(listener: Listener): boolean {
// Browser events are those which don't have `useCapture` as boolean.
return typeof listener.useCapture === 'boolean';
}
/**
* Retrieves a list of DOM listeners.
*
* ```
* <my-app>
* #VIEW
* <div (click)="doSomething()">
* </div>
* </mp-app>
*
* expect(getListeners(<div>)).toEqual({
* name: 'click',
* element: <div>,
* callback: () => doSomething(),
* useCapture: false
* });
* ```
*
* @param element Element for which the DOM listeners should be retrieved.
* @publicApi
*/
export function getListeners(element: Element): Listener[] {
const lContext = loadLContextFromNode(element);
const lView = lContext.lView;
const tView = lView[TVIEW];
const lCleanup = lView[CLEANUP];
const tCleanup = tView.cleanup;
const listeners: Listener[] = [];
if (tCleanup && lCleanup) {
for (let i = 0; i < tCleanup.length;) {
const firstParam = tCleanup[i++];
const secondParam = tCleanup[i++];
if (typeof firstParam === 'string') {
const name: string = firstParam;
const listenerElement: Element = lView[secondParam];
const callback: (value: any) => any = lCleanup[tCleanup[i++]];
const useCaptureOrIndx = tCleanup[i++];
// if useCaptureOrIndx is boolean then report it as is.
// if useCaptureOrIndx is positive number then it in unsubscribe method
// if useCaptureOrIndx is negative number then it is a Subscription
const useCapture = typeof useCaptureOrIndx === 'boolean' ?
useCaptureOrIndx :
(useCaptureOrIndx >= 0 ? false : null);
if (element == listenerElement) {
listeners.push({element, name, callback, useCapture});
}
}
}
}
listeners.sort(sortListeners);
return listeners;
}
function sortListeners(a: Listener, b: Listener) {
if (a.name == b.name) return 0;
return a.name < b.name ? -1 : 1;
}
/**
* This function should not exist because it is megamorphic and only mostly correct.
*
* See call site for more info.
*/
function isDirectiveDefHack(obj: any): obj is DirectiveDef<any> {
return obj.type !== undefined && obj.template !== undefined && obj.declaredInputs !== undefined;
}

View File

@ -8,7 +8,7 @@
import {global} from '../util'; import {global} from '../util';
import {assertDefined} from './assert'; import {assertDefined} from './assert';
import {getComponent, getDirectives, getHostElement, getInjector, getPlayers, getRootComponents, getViewComponent, markDirty} from './global_utils_api'; import {getComponent, getContext, getDirectives, getHostElement, getInjector, getListeners, getPlayers, getRootComponents, getViewComponent, markDirty} from './global_utils_api';
@ -41,6 +41,8 @@ export function publishDefaultGlobalUtils() {
if (!_published) { if (!_published) {
_published = true; _published = true;
publishGlobalUtil('getComponent', getComponent); publishGlobalUtil('getComponent', getComponent);
publishGlobalUtil('getContext', getContext);
publishGlobalUtil('getListeners', getListeners);
publishGlobalUtil('getViewComponent', getViewComponent); publishGlobalUtil('getViewComponent', getViewComponent);
publishGlobalUtil('getHostElement', getHostElement); publishGlobalUtil('getHostElement', getHostElement);
publishGlobalUtil('getInjector', getInjector); publishGlobalUtil('getInjector', getInjector);

View File

@ -15,6 +15,6 @@
* file in the public_api_guard test. * file in the public_api_guard test.
*/ */
export {getComponent, getDirectives, getHostElement, getInjector, getRootComponents, getViewComponent} from './discovery_utils'; export {getComponent, getContext, getDirectives, getHostElement, getInjector, getListeners, getRootComponents, getViewComponent} from './discovery_utils';
export {markDirty} from './instructions'; export {markDirty} from './instructions';
export {getPlayers} from './players'; export {getPlayers} from './players';

View File

@ -8,10 +8,11 @@
import {assertEqual} from './assert'; import {assertEqual} from './assert';
import {DirectiveDef} from './interfaces/definition'; import {DirectiveDef} from './interfaces/definition';
import {TNodeFlags} from './interfaces/node'; import {TNode} from './interfaces/node';
import {FLAGS, HookData, LView, LViewFlags, TView} from './interfaces/view'; import {FLAGS, HookData, LView, LViewFlags, TView} from './interfaces/view';
/** /**
* If this is the first template pass, any ngOnInit or ngDoCheck hooks will be queued into * If this is the first template pass, any ngOnInit or ngDoCheck hooks will be queued into
* TView.initHooks during directiveCreate. * TView.initHooks during directiveCreate.
@ -42,16 +43,12 @@ export function queueInitHooks(
* Loops through the directives on a node and queues all their hooks except ngOnInit * Loops through the directives on a node and queues all their hooks except ngOnInit
* and ngDoCheck, which are queued separately in directiveCreate. * and ngDoCheck, which are queued separately in directiveCreate.
*/ */
export function queueLifecycleHooks(flags: number, tView: TView): void { export function queueLifecycleHooks(tView: TView, tNode: TNode): void {
if (tView.firstTemplatePass) { if (tView.firstTemplatePass) {
const start = flags >> TNodeFlags.DirectiveStartingIndexShift;
const count = flags & TNodeFlags.DirectiveCountMask;
const end = start + count;
// It's necessary to loop through the directives at elementEnd() (rather than processing in // It's necessary to loop through the directives at elementEnd() (rather than processing in
// directiveCreate) so we can preserve the current hook order. Content, view, and destroy // directiveCreate) so we can preserve the current hook order. Content, view, and destroy
// hooks for projected components and directives must be called *before* their hosts. // hooks for projected components and directives must be called *before* their hosts.
for (let i = start; i < end; i++) { for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) {
const def = tView.data[i] as DirectiveDef<any>; const def = tView.data[i] as DirectiveDef<any>;
queueContentHooks(def, tView, i); queueContentHooks(def, tView, i);
queueViewHooks(def, tView, i); queueViewHooks(def, tView, i);

View File

@ -25,7 +25,7 @@ import {throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks'; import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container'; import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {INJECTOR_SIZE, NodeInjectorFactory} from './interfaces/injector'; import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from './interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node'; import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
import {PlayerFactory} from './interfaces/player'; import {PlayerFactory} from './interfaces/player';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
@ -112,7 +112,7 @@ export function setHostBindings(tView: TView, viewData: LView): void {
currentElementIndex = -instruction; currentElementIndex = -instruction;
// Injector block and providers are taken into account. // Injector block and providers are taken into account.
const providerCount = (tView.expandoInstructions[++i] as number); const providerCount = (tView.expandoInstructions[++i] as number);
bindingRootIndex += INJECTOR_SIZE + providerCount; bindingRootIndex += INJECTOR_BLOOM_PARENT_SIZE + providerCount;
currentDirectiveIndex = bindingRootIndex; currentDirectiveIndex = bindingRootIndex;
} else { } else {
@ -215,6 +215,7 @@ export function createNodeAtIndex(
if (tNode == null) { if (tNode == null) {
const previousOrParentTNode = getPreviousOrParentTNode(); const previousOrParentTNode = getPreviousOrParentTNode();
const isParent = getIsParent(); const isParent = getIsParent();
// TODO(misko): Refactor createTNode so that it does not depend on LView.
tNode = tView.data[adjustedIndex] = createTNode(lView, type, adjustedIndex, name, attrs, null); tNode = tView.data[adjustedIndex] = createTNode(lView, type, adjustedIndex, name, attrs, null);
// Now link ourselves into the tree. // Now link ourselves into the tree.
@ -246,10 +247,7 @@ export function createViewNode(index: number, view: LView) {
view[TVIEW].node = createTNode(view, TNodeType.View, index, null, null, null) as TViewNode; view[TVIEW].node = createTNode(view, TNodeType.View, index, null, null, null) as TViewNode;
} }
setIsParent(true); return view[HOST_NODE] = view[TVIEW].node as TViewNode;
const tNode = view[TVIEW].node as TViewNode;
setPreviousOrParentTNode(tNode);
return view[HOST_NODE] = tNode;
} }
@ -526,7 +524,7 @@ export function elementContainerEnd(): void {
lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TElementContainerNode); lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TElementContainerNode);
} }
queueLifecycleHooks(previousOrParentTNode.flags, tView); queueLifecycleHooks(tView, previousOrParentTNode);
} }
/** /**
@ -800,6 +798,9 @@ export function listener(
eventName: string, listenerFn: (e?: any) => any, useCapture = false): void { eventName: string, listenerFn: (e?: any) => any, useCapture = false): void {
const lView = getLView(); const lView = getLView();
const tNode = getPreviousOrParentTNode(); const tNode = getPreviousOrParentTNode();
const tView = lView[TVIEW];
const firstTemplatePass = tView.firstTemplatePass;
const tCleanup: false|any[] = firstTemplatePass && (tView.cleanup || (tView.cleanup = []));
ngDevMode && assertNodeOfPossibleTypes( ngDevMode && assertNodeOfPossibleTypes(
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer); tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
@ -808,47 +809,45 @@ export function listener(
const native = getNativeByTNode(tNode, lView) as RElement; const native = getNativeByTNode(tNode, lView) as RElement;
ngDevMode && ngDevMode.rendererAddEventListener++; ngDevMode && ngDevMode.rendererAddEventListener++;
const renderer = lView[RENDERER]; const renderer = lView[RENDERER];
const lCleanup = getCleanup(lView);
const lCleanupIndex = lCleanup.length;
let useCaptureOrSubIdx: boolean|number = useCapture;
// In order to match current behavior, native DOM event listeners must be added for all // In order to match current behavior, native DOM event listeners must be added for all
// events (including outputs). // events (including outputs).
if (isProceduralRenderer(renderer)) { if (isProceduralRenderer(renderer)) {
const cleanupFn = renderer.listen(native, eventName, listenerFn); const cleanupFn = renderer.listen(native, eventName, listenerFn);
storeCleanupFn(lView, cleanupFn); lCleanup.push(listenerFn, cleanupFn);
useCaptureOrSubIdx = lCleanupIndex + 1;
} else { } else {
const wrappedListener = wrapListenerWithPreventDefault(listenerFn); const wrappedListener = wrapListenerWithPreventDefault(listenerFn);
native.addEventListener(eventName, wrappedListener, useCapture); native.addEventListener(eventName, wrappedListener, useCapture);
const cleanupInstances = getCleanup(lView); lCleanup.push(wrappedListener);
cleanupInstances.push(wrappedListener);
if (getFirstTemplatePass()) {
getTViewCleanup(lView).push(
eventName, tNode.index, cleanupInstances !.length - 1, useCapture);
}
} }
tCleanup && tCleanup.push(eventName, tNode.index, lCleanupIndex, useCaptureOrSubIdx);
} }
// subscribe to directive outputs // subscribe to directive outputs
if (tNode.outputs === undefined) { if (tNode.outputs === undefined) {
// if we create TNode here, inputs must be undefined so we know they still need to be // if we create TNode here, inputs must be undefined so we know they still need to be
// checked // checked
tNode.outputs = generatePropertyAliases(tNode.flags, BindingDirection.Output); tNode.outputs = generatePropertyAliases(tNode, BindingDirection.Output);
} }
const outputs = tNode.outputs; const outputs = tNode.outputs;
let outputData: PropertyAliasValue|undefined; let props: PropertyAliasValue|undefined;
if (outputs && (outputData = outputs[eventName])) { if (outputs && (props = outputs[eventName])) {
createOutput(lView, outputData, listenerFn); const propsLength = props.length;
} if (propsLength) {
} const lCleanup = getCleanup(lView);
for (let i = 0; i < propsLength; i += 2) {
/** ngDevMode && assertDataInRange(lView, props[i] as number);
* Iterates through the outputs associated with a particular event name and subscribes to const subscription = lView[props[i] as number][props[i + 1]].subscribe(listenerFn);
* each output. const idx = lCleanup.length;
*/ lCleanup.push(listenerFn, subscription);
function createOutput(lView: LView, outputs: PropertyAliasValue, listener: Function): void { tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
for (let i = 0; i < outputs.length; i += 2) { }
ngDevMode && assertDataInRange(lView, outputs[i] as number); }
const subscription = lView[outputs[i] as number][outputs[i + 1]].subscribe(listener);
storeCleanupWithContext(lView, subscription, subscription.unsubscribe);
} }
} }
@ -860,10 +859,11 @@ function createOutput(lView: LView, outputs: PropertyAliasValue, listener: Funct
* - Index of context we just saved in LView.cleanupInstances * - Index of context we just saved in LView.cleanupInstances
*/ */
export function storeCleanupWithContext(lView: LView, context: any, cleanupFn: Function): void { export function storeCleanupWithContext(lView: LView, context: any, cleanupFn: Function): void {
getCleanup(lView).push(context); const lCleanup = getCleanup(lView);
lCleanup.push(context);
if (lView[TVIEW].firstTemplatePass) { if (lView[TVIEW].firstTemplatePass) {
getTViewCleanup(lView).push(cleanupFn, lView[CLEANUP] !.length - 1); getTViewCleanup(lView).push(cleanupFn, lCleanup.length - 1);
} }
} }
@ -900,7 +900,7 @@ export function elementEnd(): void {
lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TElementNode); lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TElementNode);
} }
queueLifecycleHooks(previousOrParentTNode.flags, getLView()[TVIEW]); queueLifecycleHooks(getLView()[TVIEW], previousOrParentTNode);
decreaseElementDepthCount(); decreaseElementDepthCount();
} }
@ -984,7 +984,7 @@ export function elementProperty<T>(
* @returns the TNode object * @returns the TNode object
*/ */
export function createTNode( export function createTNode(
viewData: LView, type: TNodeType, adjustedIndex: number, tagName: string | null, lView: LView, type: TNodeType, adjustedIndex: number, tagName: string | null,
attrs: TAttributes | null, tViews: TView[] | null): TNode { attrs: TAttributes | null, tViews: TView[] | null): TNode {
const previousOrParentTNode = getPreviousOrParentTNode(); const previousOrParentTNode = getPreviousOrParentTNode();
ngDevMode && ngDevMode.tNode++; ngDevMode && ngDevMode.tNode++;
@ -993,13 +993,15 @@ export function createTNode(
// Parents cannot cross component boundaries because components will be used in multiple places, // Parents cannot cross component boundaries because components will be used in multiple places,
// so it's only set if the view is the same. // so it's only set if the view is the same.
const parentInSameView = parent && viewData && parent !== viewData[HOST_NODE]; const parentInSameView = parent && lView && parent !== lView[HOST_NODE];
const tParent = parentInSameView ? parent as TElementNode | TContainerNode : null; const tParent = parentInSameView ? parent as TElementNode | TContainerNode : null;
return { return {
type: type, type: type,
index: adjustedIndex, index: adjustedIndex,
injectorIndex: tParent ? tParent.injectorIndex : -1, injectorIndex: tParent ? tParent.injectorIndex : -1,
directiveStart: -1,
directiveEnd: -1,
flags: 0, flags: 0,
providerIndexes: 0, providerIndexes: 0,
tagName: tagName, tagName: tagName,
@ -1044,15 +1046,13 @@ function setNgReflectProperties(lView: LView, element: RElement, propName: strin
* @param Direction direction whether to consider inputs or outputs * @param Direction direction whether to consider inputs or outputs
* @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise * @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise
*/ */
function generatePropertyAliases( function generatePropertyAliases(tNode: TNode, direction: BindingDirection): PropertyAliases|null {
tNodeFlags: TNodeFlags, direction: BindingDirection): PropertyAliases|null {
const tView = getLView()[TVIEW]; const tView = getLView()[TVIEW];
const count = tNodeFlags & TNodeFlags.DirectiveCountMask;
let propStore: PropertyAliases|null = null; let propStore: PropertyAliases|null = null;
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
if (count > 0) { if (end > start) {
const start = tNodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
const end = start + count;
const isInput = direction === BindingDirection.Input; const isInput = direction === BindingDirection.Input;
const defs = tView.data; const defs = tView.data;
@ -1093,7 +1093,7 @@ export function elementClassProp(
} }
const val = const val =
(value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory<boolean>) : (!!value); (value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory<boolean>) : (!!value);
updateElementClassProp(getStylingContext(index, getLView()), classIndex, val); updateElementClassProp(getStylingContext(index + HEADER_OFFSET, getLView()), classIndex, val);
} }
/** /**
@ -1152,14 +1152,14 @@ export function elementStyling(
if (styleDeclarations && styleDeclarations.length || if (styleDeclarations && styleDeclarations.length ||
classDeclarations && classDeclarations.length) { classDeclarations && classDeclarations.length) {
const index = tNode.index - HEADER_OFFSET; const index = tNode.index;
if (delegateToClassInput(tNode)) { if (delegateToClassInput(tNode)) {
const lView = getLView(); const lView = getLView();
const stylingContext = getStylingContext(index, lView); const stylingContext = getStylingContext(index, lView);
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string; const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
setInputsForProperty(lView, tNode.inputs !['class'] !, initialClasses); setInputsForProperty(lView, tNode.inputs !['class'] !, initialClasses);
} }
elementStylingApply(index); elementStylingApply(index - HEADER_OFFSET);
} }
} }
@ -1186,7 +1186,7 @@ export function elementStylingApply(index: number, directive?: {}): void {
const lView = getLView(); const lView = getLView();
const isFirstRender = (lView[FLAGS] & LViewFlags.CreationMode) !== 0; const isFirstRender = (lView[FLAGS] & LViewFlags.CreationMode) !== 0;
const totalPlayersQueued = renderStyleAndClassBindings( const totalPlayersQueued = renderStyleAndClassBindings(
getStylingContext(index, lView), lView[RENDERER], lView, isFirstRender); getStylingContext(index + HEADER_OFFSET, lView), lView[RENDERER], lView, isFirstRender);
if (totalPlayersQueued > 0) { if (totalPlayersQueued > 0) {
const rootContext = getRootContext(lView); const rootContext = getRootContext(lView);
scheduleTick(rootContext, RootContextFlags.FlushPlayers); scheduleTick(rootContext, RootContextFlags.FlushPlayers);
@ -1234,7 +1234,8 @@ export function elementStyleProp(
if (directive != undefined) { if (directive != undefined) {
hackImplementationOfElementStyleProp(index, styleIndex, valueToAdd, suffix, directive); hackImplementationOfElementStyleProp(index, styleIndex, valueToAdd, suffix, directive);
} else { } else {
updateElementStyleProp(getStylingContext(index, getLView()), styleIndex, valueToAdd); updateElementStyleProp(
getStylingContext(index + HEADER_OFFSET, getLView()), styleIndex, valueToAdd);
} }
} }
@ -1268,7 +1269,7 @@ export function elementStylingMap<T>(
index, classes, styles, directive); // supported in next PR index, classes, styles, directive); // supported in next PR
const lView = getLView(); const lView = getLView();
const tNode = getTNode(index, lView); const tNode = getTNode(index, lView);
const stylingContext = getStylingContext(index, lView); const stylingContext = getStylingContext(index + HEADER_OFFSET, lView);
if (delegateToClassInput(tNode) && classes !== NO_CHANGE) { if (delegateToClassInput(tNode) && classes !== NO_CHANGE) {
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string; const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
const classInputVal = const classInputVal =
@ -1482,27 +1483,26 @@ function resolveDirectives(
/** /**
* Instantiate all the directives that were previously resolved on the current node. * Instantiate all the directives that were previously resolved on the current node.
*/ */
function instantiateAllDirectives(tView: TView, viewData: LView, previousOrParentTNode: TNode) { function instantiateAllDirectives(tView: TView, lView: LView, tNode: TNode) {
const start = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift; const start = tNode.directiveStart;
const end = start + (previousOrParentTNode.flags & TNodeFlags.DirectiveCountMask); const end = tNode.directiveEnd;
if (!getFirstTemplatePass() && start < end) { if (!getFirstTemplatePass() && start < end) {
getOrCreateNodeInjectorForNode( getOrCreateNodeInjectorForNode(
previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode, viewData); tNode as TElementNode | TContainerNode | TElementContainerNode, lView);
} }
for (let i = start; i < end; i++) { for (let i = start; i < end; i++) {
const def = tView.data[i] as DirectiveDef<any>; const def = tView.data[i] as DirectiveDef<any>;
if (isComponentDef(def)) { if (isComponentDef(def)) {
addComponentLogic(viewData, previousOrParentTNode, def as ComponentDef<any>); addComponentLogic(lView, tNode, def as ComponentDef<any>);
} }
const directive = const directive = getNodeInjectable(tView.data, lView !, i, tNode as TElementNode);
getNodeInjectable(tView.data, viewData !, i, previousOrParentTNode as TElementNode); postProcessDirective(lView, directive, def, i);
postProcessDirective(viewData, directive, def, i);
} }
} }
function invokeDirectivesHostBindings(tView: TView, viewData: LView, previousOrParentTNode: TNode) { function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNode) {
const start = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift; const start = tNode.directiveStart;
const end = start + (previousOrParentTNode.flags & TNodeFlags.DirectiveCountMask); const end = tNode.directiveEnd;
const expando = tView.expandoInstructions !; const expando = tView.expandoInstructions !;
const firstTemplatePass = getFirstTemplatePass(); const firstTemplatePass = getFirstTemplatePass();
for (let i = start; i < end; i++) { for (let i = start; i < end; i++) {
@ -1511,7 +1511,7 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, previousOrP
if (def.hostBindings) { if (def.hostBindings) {
const previousExpandoLength = expando.length; const previousExpandoLength = expando.length;
setCurrentDirectiveDef(def); setCurrentDirectiveDef(def);
def.hostBindings !(RenderFlags.Create, directive, previousOrParentTNode.index); def.hostBindings !(RenderFlags.Create, directive, tNode.index);
setCurrentDirectiveDef(null); setCurrentDirectiveDef(null);
// `hostBindings` function may or may not contain `allocHostVars` call // `hostBindings` function may or may not contain `allocHostVars` call
// (e.g. it may not if it only contains host listeners), so we need to check whether // (e.g. it may not if it only contains host listeners), so we need to check whether
@ -1715,11 +1715,12 @@ export function initNodeFlags(tNode: TNode, index: number, numberOfDirectives: n
'expected node flags to not be initialized'); 'expected node flags to not be initialized');
ngDevMode && assertNotEqual( ngDevMode && assertNotEqual(
numberOfDirectives, TNodeFlags.DirectiveCountMask, numberOfDirectives, tNode.directiveEnd - tNode.directiveStart,
'Reached the max number of directives'); 'Reached the max number of directives');
// When the first directive is created on a node, save the index // When the first directive is created on a node, save the index
tNode.flags = index << TNodeFlags.DirectiveStartingIndexShift | flags & TNodeFlags.isComponent | tNode.flags = flags & TNodeFlags.isComponent;
numberOfDirectives; tNode.directiveStart = index;
tNode.directiveEnd = index + numberOfDirectives;
tNode.providerIndexes = index; tNode.providerIndexes = index;
} }
@ -1894,7 +1895,7 @@ export function template(
if (currentQueries) { if (currentQueries) {
lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TContainerNode); lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TContainerNode);
} }
queueLifecycleHooks(tNode.flags, tView); queueLifecycleHooks(tView, tNode);
setIsParent(false); setIsParent(false);
} }
@ -2842,7 +2843,7 @@ function initializeTNodeInputs(tNode: TNode | null) {
if (tNode) { if (tNode) {
if (tNode.inputs === undefined) { if (tNode.inputs === undefined) {
// mark inputs as checked // mark inputs as checked
tNode.inputs = generatePropertyAliases(tNode.flags, BindingDirection.Input); tNode.inputs = generatePropertyAliases(tNode, BindingDirection.Input);
} }
return tNode.inputs; return tNode.inputs;
} }

View File

@ -14,7 +14,7 @@ import {LView, TData} from './view';
export const TNODE = 8; export const TNODE = 8;
export const PARENT_INJECTOR = 8; export const PARENT_INJECTOR = 8;
export const INJECTOR_SIZE = 9; export const INJECTOR_BLOOM_PARENT_SIZE = 9;
/** /**
* Represents a relative location of parent injector. * Represents a relative location of parent injector.

View File

@ -28,23 +28,17 @@ export const enum TNodeType {
* Corresponds to the TNode.flags property. * Corresponds to the TNode.flags property.
*/ */
export const enum TNodeFlags { export const enum TNodeFlags {
/** The number of directives on this node is encoded on the least significant bits */
DirectiveCountMask = 0b00000000000000000000111111111111,
/** This bit is set if the node is a component */ /** This bit is set if the node is a component */
isComponent = 0b00000000000000000001000000000000, isComponent = 0b0001,
/** This bit is set if the node has been projected */ /** This bit is set if the node has been projected */
isProjected = 0b00000000000000000010000000000000, isProjected = 0b0010,
/** This bit is set if the node has any content queries */ /** This bit is set if the node has any content queries */
hasContentQuery = 0b00000000000000000100000000000000, hasContentQuery = 0b0100,
/** This bit is set if the node has any directives that contain [class properties */ /** This bit is set if the node has any directives that contain [class properties */
hasClassInput = 0b00000000000000001000000000000000, hasClassInput = 0b1000,
/** The index of the first directive on this node is encoded on the most significant bits */
DirectiveStartingIndexShift = 16,
} }
/** /**
@ -128,13 +122,17 @@ export interface TNode {
injectorIndex: number; injectorIndex: number;
/** /**
* This number stores two values using its bits: * Stores starting index of the directives.
* */
* - the number of directives on that node (first 12 bits) directiveStart: number;
* - the starting index of the node's directives in the directives array (last 20 bits).
* /**
* These two values are necessary so DI can effectively search the directives associated * Stores final exclusive index of the directives.
* with a node without searching the whole directives array. */
directiveEnd: number;
/**
* Stores if Node isComponent, isProjected, hasContentQuery and hasClassInput
*/ */
flags: TNodeFlags; flags: TNodeFlags;
@ -144,6 +142,7 @@ export interface TNode {
* - the index of the first provider on that node (first 16 bits) * - the index of the first provider on that node (first 16 bits)
* - the count of view providers from the component on this node (last 16 bits) * - the count of view providers from the component on this node (last 16 bits)
*/ */
// TODO(misko): break this into actual vars.
providerIndexes: TNodeProviderIndexes; providerIndexes: TNodeProviderIndexes;
/** The tag name associated with this node. */ /** The tag name associated with this node. */

View File

@ -446,19 +446,25 @@ export interface TView {
* saves on memory (70 bytes per array) and on a few bytes of code size (for two * saves on memory (70 bytes per array) and on a few bytes of code size (for two
* separate for loops). * separate for loops).
* *
* If it's a native DOM listener being stored: * If it's a native DOM listener or output subscription being stored:
* 1st index is: event name to remove * 1st index is: event name `name = tView.cleanup[i+0]`
* 2nd index is: index of native element in LView.data[] * 2nd index is: index of native element `element = lView[tView.cleanup[i+1]]`
* 3rd index is: index of wrapped listener function in LView.cleanupInstances[] * 3rd index is: index of listener function `listener = lView[CLEANUP][tView.cleanup[i+2]]`
* 4th index is: useCapture boolean * 4th index is: `useCaptureOrIndx = tView.cleanup[i+3]`
* `typeof useCaptureOrIndx == 'boolean' : useCapture boolean
* `typeof useCaptureOrIndx == 'number':
* `useCaptureOrIndx >= 0` `removeListener = LView[CLEANUP][useCaptureOrIndx]`
* `useCaptureOrIndx < 0` `subscription = LView[CLEANUP][-useCaptureOrIndx]`
* *
* If it's a renderer2 style listener or ViewRef destroy hook being stored: * If it's a renderer2 style listener or ViewRef destroy hook being stored:
* 1st index is: index of the cleanup function in LView.cleanupInstances[] * 1st index is: index of the cleanup function in LView.cleanupInstances[]
* 2nd index is: null * 2nd index is: `null`
* `lView[CLEANUP][tView.cleanup[i+0]]()`
* *
* If it's an output subscription or query list destroy hook: * If it's an output subscription or query list destroy hook:
* 1st index is: output unsubscribe function / query list destroy function * 1st index is: output unsubscribe function / query list destroy function
* 2nd index is: index of function context in LView.cleanupInstances[] * 2nd index is: index of function context in LView.cleanupInstances[]
* `tView.cleanup[i+0].call(lView[CLEANUP][tView.cleanup[i+1]])`
*/ */
cleanup: any[]|null; cleanup: any[]|null;

View File

@ -473,23 +473,37 @@ function cleanUpView(viewOrContainer: LView | LContainer): void {
/** Removes listeners and unsubscribes from output subscriptions */ /** Removes listeners and unsubscribes from output subscriptions */
function removeListeners(lView: LView): void { function removeListeners(lView: LView): void {
const cleanup = lView[TVIEW].cleanup !; const tCleanup = lView[TVIEW].cleanup !;
if (cleanup != null) { if (tCleanup != null) {
for (let i = 0; i < cleanup.length - 1; i += 2) { const lCleanup = lView[CLEANUP] !;
if (typeof cleanup[i] === 'string') { for (let i = 0; i < tCleanup.length - 1; i += 2) {
if (typeof tCleanup[i] === 'string') {
// This is a listener with the native renderer // This is a listener with the native renderer
const native = readElementValue(lView[cleanup[i + 1]]); const idx = tCleanup[i + 1];
const listener = lView[CLEANUP] ![cleanup[i + 2]]; const listener = lCleanup[tCleanup[i + 2]];
native.removeEventListener(cleanup[i], listener, cleanup[i + 3]); const native = readElementValue(lView[idx]);
const useCaptureOrSubIdx = tCleanup[i + 3];
if (typeof useCaptureOrSubIdx === 'boolean') {
// DOM listener
native.removeEventListener(tCleanup[i], listener, useCaptureOrSubIdx);
} else {
if (useCaptureOrSubIdx >= 0) {
// unregister
lCleanup[useCaptureOrSubIdx]();
} else {
// Subscription
lCleanup[-useCaptureOrSubIdx].unsubscribe();
}
}
i += 2; i += 2;
} else if (typeof cleanup[i] === 'number') { } else if (typeof tCleanup[i] === 'number') {
// This is a listener with renderer2 (cleanup fn can be found by index) // This is a listener with renderer2 (cleanup fn can be found by index)
const cleanupFn = lView[CLEANUP] ![cleanup[i]]; const cleanupFn = lCleanup[tCleanup[i]];
cleanupFn(); cleanupFn();
} else { } else {
// This is a cleanup function that is grouped with the index of its context // This is a cleanup function that is grouped with the index of its context
const context = lView[CLEANUP] ![cleanup[i + 1]]; const context = lCleanup[tCleanup[i + 1]];
cleanup[i].call(context); tCleanup[i].call(context);
} }
} }
lView[CLEANUP] = null; lView[CLEANUP] = null;

View File

@ -7,7 +7,7 @@
*/ */
import './ng_dev_mode'; import './ng_dev_mode';
import {getContext} from './context_discovery'; import {getLContext} from './context_discovery';
import {getRootContext} from './discovery_utils'; import {getRootContext} from './discovery_utils';
import {scheduleTick} from './instructions'; import {scheduleTick} from './instructions';
import {ComponentInstance, DirectiveInstance, Player} from './interfaces/player'; import {ComponentInstance, DirectiveInstance, Player} from './interfaces/player';
@ -29,7 +29,7 @@ import {addPlayerInternal, getOrCreatePlayerContext, getPlayerContext, getPlayer
*/ */
export function addPlayer( export function addPlayer(
ref: ComponentInstance | DirectiveInstance | HTMLElement, player: Player): void { ref: ComponentInstance | DirectiveInstance | HTMLElement, player: Player): void {
const context = getContext(ref); const context = getLContext(ref);
if (!context) { if (!context) {
ngDevMode && throwInvalidRefError(); ngDevMode && throwInvalidRefError();
return; return;
@ -54,13 +54,13 @@ export function addPlayer(
* @publicApi * @publicApi
*/ */
export function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[] { export function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[] {
const context = getContext(ref); const context = getLContext(ref);
if (!context) { if (!context) {
ngDevMode && throwInvalidRefError(); ngDevMode && throwInvalidRefError();
return []; return [];
} }
const stylingContext = getStylingContext(context.nodeIndex - HEADER_OFFSET, context.lView); const stylingContext = getStylingContext(context.nodeIndex, context.lView);
const playerContext = stylingContext ? getPlayerContext(stylingContext) : null; const playerContext = stylingContext ? getPlayerContext(stylingContext) : null;
return playerContext ? getPlayersInternal(playerContext) : []; return playerContext ? getPlayersInternal(playerContext) : [];
} }

View File

@ -668,7 +668,7 @@ function isDirty(context: StylingContext, index: number): boolean {
return ((context[adjustedIndex] as number) & StylingFlags.Dirty) == StylingFlags.Dirty; return ((context[adjustedIndex] as number) & StylingFlags.Dirty) == StylingFlags.Dirty;
} }
function isClassBased(context: StylingContext, index: number): boolean { export function isClassBased(context: StylingContext, index: number): boolean {
const adjustedIndex = const adjustedIndex =
index >= StylingIndex.SingleStylesStartPosition ? (index + StylingIndex.FlagsOffset) : index; index >= StylingIndex.SingleStylesStartPosition ? (index + StylingIndex.FlagsOffset) : index;
return ((context[adjustedIndex] as number) & StylingFlags.Class) == StylingFlags.Class; return ((context[adjustedIndex] as number) & StylingFlags.Class) == StylingFlags.Class;
@ -776,11 +776,11 @@ function getPointers(context: StylingContext, index: number): number {
return context[adjustedIndex] as number; return context[adjustedIndex] as number;
} }
function getValue(context: StylingContext, index: number): string|boolean|null { export function getValue(context: StylingContext, index: number): string|boolean|null {
return context[index + StylingIndex.ValueOffset] as string | boolean | null; return context[index + StylingIndex.ValueOffset] as string | boolean | null;
} }
function getProp(context: StylingContext, index: number): string { export function getProp(context: StylingContext, index: number): string {
return context[index + StylingIndex.PropertyOffset] as string; return context[index + StylingIndex.PropertyOffset] as string;
} }

View File

@ -8,7 +8,7 @@
import '../ng_dev_mode'; import '../ng_dev_mode';
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {getContext} from '../context_discovery'; import {getLContext} from '../context_discovery';
import {ACTIVE_INDEX, LContainer} from '../interfaces/container'; import {ACTIVE_INDEX, LContainer} from '../interfaces/container';
import {LContext} from '../interfaces/context'; import {LContext} from '../interfaces/context';
import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player'; import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player';
@ -60,7 +60,7 @@ export function allocStylingContext(
* @param viewData The view to search for the styling context * @param viewData The view to search for the styling context
*/ */
export function getStylingContext(index: number, viewData: LView): StylingContext { export function getStylingContext(index: number, viewData: LView): StylingContext {
let storageIndex = index + HEADER_OFFSET; let storageIndex = index;
let slotValue: LContainer|LView|StylingContext|RElement = viewData[storageIndex]; let slotValue: LContainer|LView|StylingContext|RElement = viewData[storageIndex];
let wrapper: LContainer|LView|StylingContext = viewData; let wrapper: LContainer|LView|StylingContext = viewData;
@ -73,7 +73,7 @@ export function getStylingContext(index: number, viewData: LView): StylingContex
return wrapper as StylingContext; return wrapper as StylingContext;
} else { } else {
// This is an LView or an LContainer // This is an LView or an LContainer
const stylingTemplate = getTNode(index, viewData).stylingTemplate; const stylingTemplate = getTNode(index - HEADER_OFFSET, viewData).stylingTemplate;
if (wrapper !== viewData) { if (wrapper !== viewData) {
storageIndex = HOST; storageIndex = HOST;
@ -85,9 +85,10 @@ export function getStylingContext(index: number, viewData: LView): StylingContex
} }
} }
function isStylingContext(value: LView | LContainer | StylingContext) { export function isStylingContext(value: any): value is StylingContext {
// Not an LView or an LContainer // Not an LView or an LContainer
return typeof value[FLAGS] !== 'number' && typeof value[ACTIVE_INDEX] !== 'number'; return Array.isArray(value) && typeof value[FLAGS] !== 'number' &&
typeof value[ACTIVE_INDEX] !== 'number';
} }
export function addPlayerInternal( export function addPlayerInternal(
@ -152,14 +153,14 @@ export function getPlayersInternal(playerContext: PlayerContext): Player[] {
export function getOrCreatePlayerContext(target: {}, context?: LContext | null): PlayerContext| export function getOrCreatePlayerContext(target: {}, context?: LContext | null): PlayerContext|
null { null {
context = context || getContext(target) !; context = context || getLContext(target) !;
if (!context) { if (!context) {
ngDevMode && throwInvalidRefError(); ngDevMode && throwInvalidRefError();
return null; return null;
} }
const {lView, nodeIndex} = context; const {lView, nodeIndex} = context;
const stylingContext = getStylingContext(nodeIndex - HEADER_OFFSET, lView); const stylingContext = getStylingContext(nodeIndex, lView);
return getPlayerContext(stylingContext) || allocPlayerContext(stylingContext); return getPlayerContext(stylingContext) || allocPlayerContext(stylingContext);
} }

View File

@ -8,7 +8,7 @@
import {global} from '../util'; import {global} from '../util';
import {assertDataInRange, assertDefined} from './assert'; import {assertDataInRange, assertDefined, assertGreaterThan, assertLessThan} from './assert';
import {ACTIVE_INDEX, LContainer} from './interfaces/container'; import {ACTIVE_INDEX, LContainer} from './interfaces/container';
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context'; import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
import {ComponentDef, DirectiveDef} from './interfaces/definition'; import {ComponentDef, DirectiveDef} from './interfaces/definition';
@ -100,6 +100,8 @@ export function getNativeByTNode(tNode: TNode, hostView: LView): RElement|RText|
} }
export function getTNode(index: number, view: LView): TNode { export function getTNode(index: number, view: LView): TNode {
ngDevMode && assertGreaterThan(index, -1, 'wrong index for TNode');
ngDevMode && assertLessThan(index, view[TVIEW].data.length, 'wrong index for TNode');
return view[TVIEW].data[index + HEADER_OFFSET] as TNode; return view[TVIEW].data[index + HEADER_OFFSET] as TNode;
} }
@ -157,6 +159,7 @@ export function getRootContext(viewOrComponent: LView | {}): RootContext {
* a component, directive or a DOM node). * a component, directive or a DOM node).
*/ */
export function readPatchedData(target: any): LView|LContext|null { export function readPatchedData(target: any): LView|LContext|null {
ngDevMode && assertDefined(target, 'Target expected');
return target[MONKEY_PATCH_KEY_NAME]; return target[MONKEY_PATCH_KEY_NAME];
} }

View File

@ -355,7 +355,7 @@ export function injectChangeDetectorRef(): ViewEngine_ChangeDetectorRef {
export function createViewRef( export function createViewRef(
hostTNode: TNode, hostView: LView, context: any): ViewEngine_ChangeDetectorRef { hostTNode: TNode, hostView: LView, context: any): ViewEngine_ChangeDetectorRef {
if (isComponent(hostTNode)) { if (isComponent(hostTNode)) {
const componentIndex = hostTNode.flags >> TNodeFlags.DirectiveStartingIndexShift; const componentIndex = hostTNode.directiveStart;
const componentView = getComponentViewByIndex(hostTNode.index, hostView); const componentView = getComponentViewByIndex(hostTNode.index, hostView);
return new ViewRef(componentView, context, componentIndex); return new ViewRef(componentView, context, componentIndex);
} else if (hostTNode.type === TNodeType.Element) { } else if (hostTNode.type === TNodeType.Element) {

View File

@ -12,7 +12,7 @@ import {stringify} from '../render3/util';
import {BypassType, allowSanitizationBypass} from './bypass'; import {BypassType, allowSanitizationBypass} from './bypass';
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer'; import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
import {SecurityContext} from './security'; import {Sanitizer, SecurityContext} from './security';
import {StyleSanitizeFn, _sanitizeStyle as _sanitizeStyle} from './style_sanitizer'; import {StyleSanitizeFn, _sanitizeStyle as _sanitizeStyle} from './style_sanitizer';
import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer'; import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
@ -32,7 +32,7 @@ import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
* and urls have been removed. * and urls have been removed.
*/ */
export function sanitizeHtml(unsafeHtml: any): string { export function sanitizeHtml(unsafeHtml: any): string {
const sanitizer = getLView()[SANITIZER]; const sanitizer = getSanitizer();
if (sanitizer) { if (sanitizer) {
return sanitizer.sanitize(SecurityContext.HTML, unsafeHtml) || ''; return sanitizer.sanitize(SecurityContext.HTML, unsafeHtml) || '';
} }
@ -56,7 +56,7 @@ export function sanitizeHtml(unsafeHtml: any): string {
* dangerous javascript and urls have been removed. * dangerous javascript and urls have been removed.
*/ */
export function sanitizeStyle(unsafeStyle: any): string { export function sanitizeStyle(unsafeStyle: any): string {
const sanitizer = getLView()[SANITIZER]; const sanitizer = getSanitizer();
if (sanitizer) { if (sanitizer) {
return sanitizer.sanitize(SecurityContext.STYLE, unsafeStyle) || ''; return sanitizer.sanitize(SecurityContext.STYLE, unsafeStyle) || '';
} }
@ -81,7 +81,7 @@ export function sanitizeStyle(unsafeStyle: any): string {
* all of the dangerous javascript has been removed. * all of the dangerous javascript has been removed.
*/ */
export function sanitizeUrl(unsafeUrl: any): string { export function sanitizeUrl(unsafeUrl: any): string {
const sanitizer = getLView()[SANITIZER]; const sanitizer = getSanitizer();
if (sanitizer) { if (sanitizer) {
return sanitizer.sanitize(SecurityContext.URL, unsafeUrl) || ''; return sanitizer.sanitize(SecurityContext.URL, unsafeUrl) || '';
} }
@ -101,7 +101,7 @@ export function sanitizeUrl(unsafeUrl: any): string {
* only trusted `url`s have been allowed to pass. * only trusted `url`s have been allowed to pass.
*/ */
export function sanitizeResourceUrl(unsafeResourceUrl: any): string { export function sanitizeResourceUrl(unsafeResourceUrl: any): string {
const sanitizer = getLView()[SANITIZER]; const sanitizer = getSanitizer();
if (sanitizer) { if (sanitizer) {
return sanitizer.sanitize(SecurityContext.RESOURCE_URL, unsafeResourceUrl) || ''; return sanitizer.sanitize(SecurityContext.RESOURCE_URL, unsafeResourceUrl) || '';
} }
@ -122,7 +122,7 @@ export function sanitizeResourceUrl(unsafeResourceUrl: any): string {
* because only trusted `scripts` have been allowed to pass. * because only trusted `scripts` have been allowed to pass.
*/ */
export function sanitizeScript(unsafeScript: any): string { export function sanitizeScript(unsafeScript: any): string {
const sanitizer = getLView()[SANITIZER]; const sanitizer = getSanitizer();
if (sanitizer) { if (sanitizer) {
return sanitizer.sanitize(SecurityContext.SCRIPT, unsafeScript) || ''; return sanitizer.sanitize(SecurityContext.SCRIPT, unsafeScript) || '';
} }
@ -145,3 +145,8 @@ export const defaultStyleSanitizer = (function(prop: string, value?: string): st
return sanitizeStyle(value); return sanitizeStyle(value);
} as StyleSanitizeFn); } as StyleSanitizeFn);
function getSanitizer(): Sanitizer|null {
const lView = getLView();
return lView && lView[SANITIZER];
}

View File

@ -84,7 +84,7 @@
"name": "INJECTOR" "name": "INJECTOR"
}, },
{ {
"name": "INJECTOR_SIZE" "name": "INJECTOR_BLOOM_PARENT_SIZE"
}, },
{ {
"name": "InjectFlags" "name": "InjectFlags"
@ -416,9 +416,6 @@
{ {
"name": "createNodeAtIndex" "name": "createNodeAtIndex"
}, },
{
"name": "createOutput"
},
{ {
"name": "createRootComponent" "name": "createRootComponent"
}, },
@ -605,21 +602,12 @@
{ {
"name": "getContainerRenderParent" "name": "getContainerRenderParent"
}, },
{
"name": "getContext"
},
{ {
"name": "getCreationMode" "name": "getCreationMode"
}, },
{ {
"name": "getDirectiveDef" "name": "getDirectiveDef"
}, },
{
"name": "getDirectiveEndIndex"
},
{
"name": "getDirectiveStartIndex"
},
{ {
"name": "getDirectivesAtNodeIndex" "name": "getDirectivesAtNodeIndex"
}, },
@ -656,6 +644,9 @@
{ {
"name": "getLContainer" "name": "getLContainer"
}, },
{
"name": "getLContext"
},
{ {
"name": "getLView" "name": "getLView"
}, },
@ -924,7 +915,7 @@
"name": "listener" "name": "listener"
}, },
{ {
"name": "loadContext" "name": "loadLContext"
}, },
{ {
"name": "locateDirectiveOrProvider" "name": "locateDirectiveOrProvider"
@ -1148,9 +1139,6 @@
{ {
"name": "storeCleanupFn" "name": "storeCleanupFn"
}, },
{
"name": "storeCleanupWithContext"
},
{ {
"name": "stringify" "name": "stringify"
}, },

View File

@ -51,7 +51,7 @@
"name": "INJECTOR" "name": "INJECTOR"
}, },
{ {
"name": "INJECTOR_SIZE" "name": "INJECTOR_BLOOM_PARENT_SIZE"
}, },
{ {
"name": "MONKEY_PATCH_KEY_NAME" "name": "MONKEY_PATCH_KEY_NAME"

View File

@ -171,7 +171,7 @@
"name": "INJECTOR$1" "name": "INJECTOR$1"
}, },
{ {
"name": "INJECTOR_SIZE" "name": "INJECTOR_BLOOM_PARENT_SIZE"
}, },
{ {
"name": "Inject" "name": "Inject"

View File

@ -72,7 +72,7 @@
"name": "INJECTOR" "name": "INJECTOR"
}, },
{ {
"name": "INJECTOR_SIZE" "name": "INJECTOR_BLOOM_PARENT_SIZE"
}, },
{ {
"name": "InjectFlags" "name": "InjectFlags"
@ -473,9 +473,6 @@
{ {
"name": "createNodeAtIndex" "name": "createNodeAtIndex"
}, },
{
"name": "createOutput"
},
{ {
"name": "createRootComponent" "name": "createRootComponent"
}, },
@ -1169,9 +1166,6 @@
{ {
"name": "storeCleanupFn" "name": "storeCleanupFn"
}, },
{
"name": "storeCleanupWithContext"
},
{ {
"name": "stringify" "name": "stringify"
}, },

View File

@ -381,7 +381,7 @@
"name": "INJECTOR$1" "name": "INJECTOR$1"
}, },
{ {
"name": "INJECTOR_SIZE" "name": "INJECTOR_BLOOM_PARENT_SIZE"
}, },
{ {
"name": "INSPECT_GLOBAL_NAME" "name": "INSPECT_GLOBAL_NAME"
@ -1379,9 +1379,6 @@
{ {
"name": "createNodeAtIndex" "name": "createNodeAtIndex"
}, },
{
"name": "createOutput"
},
{ {
"name": "createPlatform" "name": "createPlatform"
}, },
@ -1736,12 +1733,6 @@
{ {
"name": "getDirectiveDef" "name": "getDirectiveDef"
}, },
{
"name": "getDirectiveEndIndex"
},
{
"name": "getDirectiveStartIndex"
},
{ {
"name": "getDirectivesAtNodeIndex" "name": "getDirectivesAtNodeIndex"
}, },
@ -1775,6 +1766,9 @@
{ {
"name": "getInjectableDef" "name": "getInjectableDef"
}, },
{
"name": "getInjectionTokens"
},
{ {
"name": "getInjector" "name": "getInjector"
}, },
@ -1790,6 +1784,9 @@
{ {
"name": "getLContainer" "name": "getLContainer"
}, },
{
"name": "getLContext"
},
{ {
"name": "getLView" "name": "getLView"
}, },
@ -1799,6 +1796,9 @@
{ {
"name": "getLastDefinedValue" "name": "getLastDefinedValue"
}, },
{
"name": "getListeners"
},
{ {
"name": "getLocalRefs" "name": "getLocalRefs"
}, },
@ -2120,6 +2120,12 @@
{ {
"name": "isBlackListedEvent" "name": "isBlackListedEvent"
}, },
{
"name": "isBrowserEvents"
},
{
"name": "isClassBased"
},
{ {
"name": "isComponent" "name": "isComponent"
}, },
@ -2256,10 +2262,13 @@
"name": "listener" "name": "listener"
}, },
{ {
"name": "loadContext" "name": "loadInternal"
}, },
{ {
"name": "loadInternal" "name": "loadLContext"
},
{
"name": "loadLContextFromNode"
}, },
{ {
"name": "localeEn" "name": "localeEn"
@ -2348,9 +2357,6 @@
{ {
"name": "noopScope" "name": "noopScope"
}, },
{
"name": "notImplemented"
},
{ {
"name": "observable" "name": "observable"
}, },
@ -2621,6 +2627,9 @@
{ {
"name": "shouldSearchParent" "name": "shouldSearchParent"
}, },
{
"name": "sortListeners"
},
{ {
"name": "staticError" "name": "staticError"
}, },
@ -2633,9 +2642,6 @@
{ {
"name": "storeCleanupFn" "name": "storeCleanupFn"
}, },
{
"name": "storeCleanupWithContext"
},
{ {
"name": "strToNumber" "name": "strToNumber"
}, },

View File

@ -13,7 +13,6 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by'; import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {fixmeIvy} from '@angular/private/testing';
@Injectable() @Injectable()
class Logger { class Logger {
@ -259,35 +258,32 @@ class TestApp {
expect(list.children.length).toEqual(3); expect(list.children.length).toEqual(3);
}); });
fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && it('should list element attributes', () => {
it('should list element attributes', () => { fixture = TestBed.createComponent(TestApp);
fixture = TestBed.createComponent(TestApp); fixture.detectChanges();
fixture.detectChanges(); const bankElem = fixture.debugElement.children[0];
const bankElem = fixture.debugElement.children[0];
expect(bankElem.attributes['bank']).toEqual('RBC'); expect(bankElem.attributes['bank']).toEqual('RBC');
expect(bankElem.attributes['account']).toEqual('4747'); expect(bankElem.attributes['account']).toEqual('4747');
}); });
fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && it('should list element classes', () => {
it('should list element classes', () => { fixture = TestBed.createComponent(TestApp);
fixture = TestBed.createComponent(TestApp); fixture.detectChanges();
fixture.detectChanges(); const bankElem = fixture.debugElement.children[0];
const bankElem = fixture.debugElement.children[0];
expect(bankElem.classes['closed']).toBe(true); expect(bankElem.classes['closed']).toBe(true);
expect(bankElem.classes['open']).toBe(false); expect(bankElem.classes['open']).toBe(false);
}); });
fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && it('should list element styles', () => {
it('should list element styles', () => { fixture = TestBed.createComponent(TestApp);
fixture = TestBed.createComponent(TestApp); fixture.detectChanges();
fixture.detectChanges(); const bankElem = fixture.debugElement.children[0];
const bankElem = fixture.debugElement.children[0];
expect(bankElem.styles['width']).toEqual('200px'); expect(bankElem.styles['width']).toEqual('200px');
expect(bankElem.styles['color']).toEqual('red'); expect(bankElem.styles['color']).toEqual('red');
}); });
it('should query child elements by css', () => { it('should query child elements by css', () => {
fixture = TestBed.createComponent(ParentComp); fixture = TestBed.createComponent(ParentComp);
@ -312,13 +308,12 @@ class TestApp {
expect(getDOM().hasClass(childTestEls[3].nativeElement, 'childnested')).toBe(true); expect(getDOM().hasClass(childTestEls[3].nativeElement, 'childnested')).toBe(true);
}); });
fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && it('should list providerTokens', () => {
it('should list providerTokens', () => { fixture = TestBed.createComponent(ParentComp);
fixture = TestBed.createComponent(ParentComp); fixture.detectChanges();
fixture.detectChanges();
expect(fixture.debugElement.providerTokens).toContain(Logger); expect(fixture.debugElement.providerTokens).toContain(Logger);
}); });
it('should list locals', () => { it('should list locals', () => {
fixture = TestBed.createComponent(LocalsComp); fixture = TestBed.createComponent(LocalsComp);
@ -327,25 +322,22 @@ class TestApp {
expect(fixture.debugElement.children[0].references !['alice']).toBeAnInstanceOf(MyDir); expect(fixture.debugElement.children[0].references !['alice']).toBeAnInstanceOf(MyDir);
}); });
fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && it('should allow injecting from the element injector', () => {
it('should allow injecting from the element injector', () => { fixture = TestBed.createComponent(ParentComp);
fixture = TestBed.createComponent(ParentComp); fixture.detectChanges();
fixture.detectChanges();
expect((<Logger>(fixture.debugElement.children[0].injector.get(Logger))).logs).toEqual([ expect((<Logger>(fixture.debugElement.children[0].injector.get(Logger))).logs).toEqual([
'parent', 'nestedparent', 'child', 'nestedchild' 'parent', 'nestedparent', 'child', 'nestedchild'
]); ]);
}); });
fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') && it('should list event listeners', () => {
it('should list event listeners', () => { fixture = TestBed.createComponent(EventsComp);
fixture = TestBed.createComponent(EventsComp); fixture.detectChanges();
fixture.detectChanges();
expect(fixture.debugElement.children[0].listeners.length).toEqual(1); expect(fixture.debugElement.children[0].listeners.length).toEqual(1);
expect(fixture.debugElement.children[1].listeners.length).toEqual(1); expect(fixture.debugElement.children[1].listeners.length).toEqual(1);
});
});
it('should trigger event handlers', () => { it('should trigger event handlers', () => {
fixture = TestBed.createComponent(EventsComp); fixture = TestBed.createComponent(EventsComp);

View File

@ -5,18 +5,16 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {ReferenceFilter} from '@angular/compiler';
import {createInjector} from '@angular/core'; import {createInjector} from '@angular/core';
import {StaticInjector} from '../../src/di/injector'; import {StaticInjector} from '../../src/di/injector';
import {getComponent, getDirectives, getInjector, getLocalRefs, getRootComponents, getViewComponent} from '../../src/render3/discovery_utils'; import {getComponent, getContext, getDirectives, getInjectionTokens, getInjector, getListeners, getLocalRefs, getRootComponents, getViewComponent} from '../../src/render3/discovery_utils';
import {ProvidersFeature, RenderFlags, defineComponent, defineDirective, getHostElement} from '../../src/render3/index'; import {ProvidersFeature, RenderFlags, defineComponent, defineDirective, getHostElement} from '../../src/render3/index';
import {container, element, elementEnd, elementStart, elementStyling, elementStylingApply, template, bind, elementProperty, text, textBinding, markDirty} from '../../src/render3/instructions'; import {element, elementEnd, elementStart, elementStyling, elementStylingApply, template, bind, elementProperty, text, textBinding, markDirty, listener} from '../../src/render3/instructions';
import {ComponentFixture} from './render_util'; import {ComponentFixture} from './render_util';
import {NgIf} from './common_with_def'; import {NgIf} from './common_with_def';
import {getRootContext} from '@angular/core/src/render3/util';
describe('discovery utils', () => { describe('discovery utils', () => {
let fixture: ComponentFixture<MyApp>; let fixture: ComponentFixture<MyApp>;
@ -27,8 +25,10 @@ describe('discovery utils', () => {
let span: NodeListOf<Element>; let span: NodeListOf<Element>;
let div: NodeListOf<Element>; let div: NodeListOf<Element>;
let p: NodeListOf<Element>; let p: NodeListOf<Element>;
let log: any[];
beforeEach(() => { beforeEach(() => {
log = [];
myApp = []; myApp = [];
dirA = []; dirA = [];
childComponent = []; childComponent = [];
@ -46,7 +46,7 @@ describe('discovery utils', () => {
* ``` * ```
* <my-app> * <my-app>
* <#VIEW> * <#VIEW>
* <span>{{text}}</span> * <span (click)=" log.push($event) ">{{text}}</span>
* <div dirA #div #foo="dirA"></div> * <div dirA #div #foo="dirA"></div>
* <child> * <child>
* <#VIEW> * <#VIEW>
@ -110,6 +110,7 @@ describe('discovery utils', () => {
template: (rf: RenderFlags, ctx: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
listener('click', $event => log.push($event));
text(1); text(1);
elementEnd(); elementEnd();
element(2, 'div', ['dirA', ''], ['div', '', 'foo', 'dirA']); element(2, 'div', ['dirA', ''], ['div', '', 'foo', 'dirA']);
@ -146,6 +147,18 @@ describe('discovery utils', () => {
}); });
}); });
describe('getContext', () => {
it('should throw when called on non-element', () => {
expect(() => getContext(dirA[0] as any)).toThrowError(/Expecting instance of DOM Node/);
expect(() => getContext(dirA[1] as any)).toThrowError(/Expecting instance of DOM Node/);
});
it('should return context from element', () => {
expect(getContext<MyApp>(child[0])).toEqual(myApp[0]);
expect(getContext<{$implicit: boolean}>(child[2]) !.$implicit).toEqual(true);
expect(getContext<Child>(p[0])).toEqual(childComponent[0]);
});
});
describe('getHostElement', () => { describe('getHostElement', () => {
it('should return element on component', () => { it('should return element on component', () => {
expect(getHostElement(myApp[0])).toEqual(fixture.hostElement); expect(getHostElement(myApp[0])).toEqual(fixture.hostElement);
@ -217,7 +230,7 @@ describe('discovery utils', () => {
}); });
}); });
describe('localRefs', () => { describe('getLocalRefs', () => {
it('should retrieve empty map', () => { it('should retrieve empty map', () => {
expect(getLocalRefs(fixture.hostElement)).toEqual({}); expect(getLocalRefs(fixture.hostElement)).toEqual({});
expect(getLocalRefs(myApp[0])).toEqual({}); expect(getLocalRefs(myApp[0])).toEqual({});
@ -249,6 +262,30 @@ describe('discovery utils', () => {
}); });
}); });
describe('getListeners', () => {
it('should return no listeners', () => {
expect(getListeners(fixture.hostElement)).toEqual([]);
expect(getListeners(child[0])).toEqual([]);
});
it('should return the listeners', () => {
const listeners = getListeners(span[0]);
expect(listeners.length).toEqual(1);
expect(listeners[0].name).toEqual('click');
expect(listeners[0].element).toEqual(span[0]);
expect(listeners[0].useCapture).toEqual(false);
listeners[0].callback('CLICKED');
expect(log).toEqual(['CLICKED']);
});
});
describe('getInjectionTokens', () => {
it('should retrieve tokens', () => {
expect(getInjectionTokens(fixture.hostElement)).toEqual([MyApp]);
expect(getInjectionTokens(child[0])).toEqual([String, Child]);
expect(getInjectionTokens(child[1])).toEqual([String, Child, DirectiveA]);
});
});
describe('markDirty', () => { describe('markDirty', () => {
it('should re-render component', () => { it('should re-render component', () => {
expect(span[0].textContent).toEqual('INIT'); expect(span[0].textContent).toEqual('INIT');

View File

@ -7,7 +7,7 @@
*/ */
import {ɵmarkDirty as markDirty} from '@angular/core'; import {ɵmarkDirty as markDirty} from '@angular/core';
import {getComponent, getDirectives, getHostElement, getInjector, getRootComponents, getViewComponent} from '../../src/render3/discovery_utils'; import {getComponent, getContext, getDirectives, getHostElement, getInjector, getListeners, getRootComponents, getViewComponent} from '../../src/render3/discovery_utils';
import {GLOBAL_PUBLISH_EXPANDO_KEY, GlobalDevModeContainer, publishDefaultGlobalUtils, publishGlobalUtil} from '../../src/render3/global_utils'; import {GLOBAL_PUBLISH_EXPANDO_KEY, GlobalDevModeContainer, publishDefaultGlobalUtils, publishGlobalUtil} from '../../src/render3/global_utils';
import {getPlayers} from '../../src/render3/players'; import {getPlayers} from '../../src/render3/players';
import {global} from '../../src/util'; import {global} from '../../src/util';
@ -28,6 +28,10 @@ describe('global utils', () => {
it('should publish getComponent', () => { assertPublished('getComponent', getComponent); }); it('should publish getComponent', () => { assertPublished('getComponent', getComponent); });
it('should publish getContext', () => { assertPublished('getContext', getContext); });
it('should publish getListeners', () => { assertPublished('getListeners', getListeners); });
it('should publish getViewComponent', it('should publish getViewComponent',
() => { assertPublished('getViewComponent', getViewComponent); }); () => { assertPublished('getViewComponent', getViewComponent); });

View File

@ -659,7 +659,7 @@ describe('host bindings', () => {
selectors: [['', 'hostDir', '']], selectors: [['', 'hostDir', '']],
factory: () => new HostBindingDir(), factory: () => new HostBindingDir(),
hostBindings: (rf: RenderFlags, ctx: HostBindingDir, elIndex: number) => { hostBindings: (rf: RenderFlags, ctx: HostBindingDir, elIndex: number) => {
// LViewData [..., title, ctx.title, pf1] // LView [..., title, ctx.title, pf1]
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
allocHostVars(3); allocHostVars(3);
} }

View File

@ -21,7 +21,7 @@ import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
import {NgIf} from './common_with_def'; import {NgIf} from './common_with_def';
import {ComponentFixture, TemplateFixture, createComponent, renderToHtml} from './render_util'; import {ComponentFixture, TemplateFixture, createComponent, renderToHtml} from './render_util';
import {getContext} from '../../src/render3/context_discovery'; import {getLContext} from '../../src/render3/context_discovery';
import {StylingIndex} from '../../src/render3/interfaces/styling'; import {StylingIndex} from '../../src/render3/interfaces/styling';
import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context'; import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context';
@ -2041,14 +2041,14 @@ describe('render3 integration test', () => {
fixture.update(); fixture.update();
const section = fixture.hostElement.querySelector('section') !; const section = fixture.hostElement.querySelector('section') !;
const sectionContext = getContext(section) !; const sectionContext = getLContext(section) !;
const sectionLView = sectionContext.lView !; const sectionLView = sectionContext.lView !;
expect(sectionContext.nodeIndex).toEqual(HEADER_OFFSET); expect(sectionContext.nodeIndex).toEqual(HEADER_OFFSET);
expect(sectionLView.length).toBeGreaterThan(HEADER_OFFSET); expect(sectionLView.length).toBeGreaterThan(HEADER_OFFSET);
expect(sectionContext.native).toBe(section); expect(sectionContext.native).toBe(section);
const div = fixture.hostElement.querySelector('div') !; const div = fixture.hostElement.querySelector('div') !;
const divContext = getContext(div) !; const divContext = getLContext(div) !;
const divLView = divContext.lView !; const divLView = divContext.lView !;
expect(divContext.nodeIndex).toEqual(HEADER_OFFSET + 1); expect(divContext.nodeIndex).toEqual(HEADER_OFFSET + 1);
expect(divLView.length).toBeGreaterThan(HEADER_OFFSET); expect(divLView.length).toBeGreaterThan(HEADER_OFFSET);
@ -2080,7 +2080,7 @@ describe('render3 integration test', () => {
const result1 = section[MONKEY_PATCH_KEY_NAME]; const result1 = section[MONKEY_PATCH_KEY_NAME];
expect(Array.isArray(result1)).toBeTruthy(); expect(Array.isArray(result1)).toBeTruthy();
const context = getContext(section) !; const context = getLContext(section) !;
const result2 = section[MONKEY_PATCH_KEY_NAME]; const result2 = section[MONKEY_PATCH_KEY_NAME];
expect(Array.isArray(result2)).toBeFalsy(); expect(Array.isArray(result2)).toBeFalsy();
@ -2116,7 +2116,7 @@ describe('render3 integration test', () => {
const p = fixture.hostElement.querySelector('p') !as any; const p = fixture.hostElement.querySelector('p') !as any;
expect(p[MONKEY_PATCH_KEY_NAME]).toBeFalsy(); expect(p[MONKEY_PATCH_KEY_NAME]).toBeFalsy();
const pContext = getContext(p) !; const pContext = getLContext(p) !;
expect(pContext.native).toBe(p); expect(pContext.native).toBe(p);
expect(p[MONKEY_PATCH_KEY_NAME]).toBe(pContext); expect(p[MONKEY_PATCH_KEY_NAME]).toBe(pContext);
}); });
@ -2154,7 +2154,7 @@ describe('render3 integration test', () => {
expect(Array.isArray(elementResult)).toBeTruthy(); expect(Array.isArray(elementResult)).toBeTruthy();
expect(elementResult[StylingIndex.ElementPosition]).toBe(section); expect(elementResult[StylingIndex.ElementPosition]).toBe(section);
const context = getContext(section) !; const context = getLContext(section) !;
const result2 = section[MONKEY_PATCH_KEY_NAME]; const result2 = section[MONKEY_PATCH_KEY_NAME];
expect(Array.isArray(result2)).toBeFalsy(); expect(Array.isArray(result2)).toBeFalsy();
@ -2248,9 +2248,9 @@ describe('render3 integration test', () => {
expect(pText[MONKEY_PATCH_KEY_NAME]).toBeFalsy(); expect(pText[MONKEY_PATCH_KEY_NAME]).toBeFalsy();
expect(projectedTextNode[MONKEY_PATCH_KEY_NAME]).toBeTruthy(); expect(projectedTextNode[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
const parentContext = getContext(section) !; const parentContext = getLContext(section) !;
const shadowContext = getContext(header) !; const shadowContext = getLContext(header) !;
const projectedContext = getContext(p) !; const projectedContext = getLContext(p) !;
const parentComponentData = parentContext.lView; const parentComponentData = parentContext.lView;
const shadowComponentData = shadowContext.lView; const shadowComponentData = shadowContext.lView;
@ -2263,12 +2263,12 @@ describe('render3 integration test', () => {
it('should return `null` when an element context is retrieved that isn\'t situated in Angular', it('should return `null` when an element context is retrieved that isn\'t situated in Angular',
() => { () => {
const elm1 = document.createElement('div'); const elm1 = document.createElement('div');
const context1 = getContext(elm1); const context1 = getLContext(elm1);
expect(context1).toBeFalsy(); expect(context1).toBeFalsy();
const elm2 = document.createElement('div'); const elm2 = document.createElement('div');
document.body.appendChild(elm2); document.body.appendChild(elm2);
const context2 = getContext(elm2); const context2 = getLContext(elm2);
expect(context2).toBeFalsy(); expect(context2).toBeFalsy();
}); });
@ -2296,7 +2296,7 @@ describe('render3 integration test', () => {
const manuallyCreatedElement = document.createElement('div'); const manuallyCreatedElement = document.createElement('div');
section.appendChild(manuallyCreatedElement); section.appendChild(manuallyCreatedElement);
const context = getContext(manuallyCreatedElement); const context = getLContext(manuallyCreatedElement);
expect(context).toBeFalsy(); expect(context).toBeFalsy();
}); });
@ -2324,11 +2324,11 @@ describe('render3 integration test', () => {
const hostLView = (hostElm as any)[MONKEY_PATCH_KEY_NAME]; const hostLView = (hostElm as any)[MONKEY_PATCH_KEY_NAME];
expect(hostLView).toBe(componentLView); expect(hostLView).toBe(componentLView);
const context1 = getContext(hostElm) !; const context1 = getLContext(hostElm) !;
expect(context1.lView).toBe(hostLView); expect(context1.lView).toBe(hostLView);
expect(context1.native).toEqual(hostElm); expect(context1.native).toEqual(hostElm);
const context2 = getContext(component) !; const context2 = getLContext(component) !;
expect(context2).toBe(context1); expect(context2).toBe(context1);
expect(context2.lView).toBe(hostLView); expect(context2.lView).toBe(hostLView);
expect(context2.native).toEqual(hostElm); expect(context2.native).toEqual(hostElm);
@ -2387,7 +2387,7 @@ describe('render3 integration test', () => {
const hostElm = fixture.hostElement; const hostElm = fixture.hostElement;
const div1 = hostElm.querySelector('div:first-child') !as any; const div1 = hostElm.querySelector('div:first-child') !as any;
const div2 = hostElm.querySelector('div:last-child') !as any; const div2 = hostElm.querySelector('div:last-child') !as any;
const context = getContext(hostElm) !; const context = getLContext(hostElm) !;
const componentView = context.lView[context.nodeIndex]; const componentView = context.lView[context.nodeIndex];
expect(componentView).toContain(myDir1Instance); expect(componentView).toContain(myDir1Instance);
@ -2398,9 +2398,9 @@ describe('render3 integration test', () => {
expect(Array.isArray((myDir2Instance 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(); expect(Array.isArray((myDir3Instance as any)[MONKEY_PATCH_KEY_NAME])).toBeTruthy();
const d1Context = getContext(myDir1Instance) !; const d1Context = getLContext(myDir1Instance) !;
const d2Context = getContext(myDir2Instance) !; const d2Context = getLContext(myDir2Instance) !;
const d3Context = getContext(myDir3Instance) !; const d3Context = getLContext(myDir3Instance) !;
expect(d1Context.lView).toEqual(componentView); expect(d1Context.lView).toEqual(componentView);
expect(d2Context.lView).toEqual(componentView); expect(d2Context.lView).toEqual(componentView);
@ -2487,28 +2487,28 @@ describe('render3 integration test', () => {
expect((myDir2Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(lView); expect((myDir2Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(lView);
expect((childComponentInstance as any)[MONKEY_PATCH_KEY_NAME]).toBe(lView); expect((childComponentInstance as any)[MONKEY_PATCH_KEY_NAME]).toBe(lView);
const childNodeContext = getContext(childCompHostElm) !; const childNodeContext = getLContext(childCompHostElm) !;
expect(childNodeContext.component).toBeFalsy(); expect(childNodeContext.component).toBeFalsy();
expect(childNodeContext.directives).toBeFalsy(); expect(childNodeContext.directives).toBeFalsy();
assertMonkeyPatchValueIsLView(myDir1Instance); assertMonkeyPatchValueIsLView(myDir1Instance);
assertMonkeyPatchValueIsLView(myDir2Instance); assertMonkeyPatchValueIsLView(myDir2Instance);
assertMonkeyPatchValueIsLView(childComponentInstance); assertMonkeyPatchValueIsLView(childComponentInstance);
expect(getContext(myDir1Instance)).toBe(childNodeContext); expect(getLContext(myDir1Instance)).toBe(childNodeContext);
expect(childNodeContext.component).toBeFalsy(); expect(childNodeContext.component).toBeFalsy();
expect(childNodeContext.directives !.length).toEqual(2); expect(childNodeContext.directives !.length).toEqual(2);
assertMonkeyPatchValueIsLView(myDir1Instance, false); assertMonkeyPatchValueIsLView(myDir1Instance, false);
assertMonkeyPatchValueIsLView(myDir2Instance, false); assertMonkeyPatchValueIsLView(myDir2Instance, false);
assertMonkeyPatchValueIsLView(childComponentInstance); assertMonkeyPatchValueIsLView(childComponentInstance);
expect(getContext(myDir2Instance)).toBe(childNodeContext); expect(getLContext(myDir2Instance)).toBe(childNodeContext);
expect(childNodeContext.component).toBeFalsy(); expect(childNodeContext.component).toBeFalsy();
expect(childNodeContext.directives !.length).toEqual(2); expect(childNodeContext.directives !.length).toEqual(2);
assertMonkeyPatchValueIsLView(myDir1Instance, false); assertMonkeyPatchValueIsLView(myDir1Instance, false);
assertMonkeyPatchValueIsLView(myDir2Instance, false); assertMonkeyPatchValueIsLView(myDir2Instance, false);
assertMonkeyPatchValueIsLView(childComponentInstance); assertMonkeyPatchValueIsLView(childComponentInstance);
expect(getContext(childComponentInstance)).toBe(childNodeContext); expect(getLContext(childComponentInstance)).toBe(childNodeContext);
expect(childNodeContext.component).toBeTruthy(); expect(childNodeContext.component).toBeTruthy();
expect(childNodeContext.directives !.length).toEqual(2); expect(childNodeContext.directives !.length).toEqual(2);
assertMonkeyPatchValueIsLView(myDir1Instance, false); assertMonkeyPatchValueIsLView(myDir1Instance, false);
@ -2565,7 +2565,7 @@ describe('render3 integration test', () => {
const child = host.querySelector('child-comp') as any; const child = host.querySelector('child-comp') as any;
expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy(); expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
const context = getContext(child) !; const context = getLContext(child) !;
expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy(); expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
const componentData = context.lView[context.nodeIndex]; const componentData = context.lView[context.nodeIndex];
@ -2573,7 +2573,7 @@ describe('render3 integration test', () => {
expect(component instanceof ChildComp).toBeTruthy(); expect(component instanceof ChildComp).toBeTruthy();
expect(component[MONKEY_PATCH_KEY_NAME]).toBe(context.lView); expect(component[MONKEY_PATCH_KEY_NAME]).toBe(context.lView);
const componentContext = getContext(component) !; const componentContext = getLContext(component) !;
expect(component[MONKEY_PATCH_KEY_NAME]).toBe(componentContext); expect(component[MONKEY_PATCH_KEY_NAME]).toBe(componentContext);
expect(componentContext.nodeIndex).toEqual(context.nodeIndex); expect(componentContext.nodeIndex).toEqual(context.nodeIndex);
expect(componentContext.native).toEqual(context.native); expect(componentContext.native).toEqual(context.native);

View File

@ -21,7 +21,7 @@ import {SWITCH_TEMPLATE_REF_FACTORY__POST_R3__ as R3_TEMPLATE_REF_FACTORY} from
import {SWITCH_VIEW_CONTAINER_REF_FACTORY__POST_R3__ as R3_VIEW_CONTAINER_REF_FACTORY} from '../../src/linker/view_container_ref'; import {SWITCH_VIEW_CONTAINER_REF_FACTORY__POST_R3__ as R3_VIEW_CONTAINER_REF_FACTORY} from '../../src/linker/view_container_ref';
import {SWITCH_RENDERER2_FACTORY__POST_R3__ as R3_RENDERER2_FACTORY} from '../../src/render/api'; import {SWITCH_RENDERER2_FACTORY__POST_R3__ as R3_RENDERER2_FACTORY} from '../../src/render/api';
import {CreateComponentOptions} from '../../src/render3/component'; import {CreateComponentOptions} from '../../src/render3/component';
import {getContext, getDirectivesAtNodeIndex, isComponentInstance} from '../../src/render3/context_discovery'; import {getDirectivesAtNodeIndex, getLContext, isComponentInstance} from '../../src/render3/context_discovery';
import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition'; import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition';
import {NG_ELEMENT_ID} from '../../src/render3/fields'; import {NG_ELEMENT_ID} from '../../src/render3/fields';
import {ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index'; import {ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index';
@ -266,7 +266,7 @@ export function renderComponent<T>(type: ComponentType<T>, opts?: CreateComponen
export function toHtml<T>(componentOrElement: T | RElement, keepNgReflect = false): string { export function toHtml<T>(componentOrElement: T | RElement, keepNgReflect = false): string {
let element: any; let element: any;
if (isComponentInstance(componentOrElement)) { if (isComponentInstance(componentOrElement)) {
const context = getContext(componentOrElement); const context = getLContext(componentOrElement);
element = context ? context.native : null; element = context ? context.native : null;
} else { } else {
element = componentOrElement; element = componentOrElement;

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {createRootContext} from '../../../src/render3/component'; import {createRootContext} from '../../../src/render3/component';
import {getContext} from '../../../src/render3/context_discovery'; import {getLContext} from '../../../src/render3/context_discovery';
import {defineComponent} from '../../../src/render3/index'; import {defineComponent} from '../../../src/render3/index';
import {createLView, createTView, elementClassProp, elementEnd, elementStart, elementStyleProp, elementStyling, elementStylingApply, elementStylingMap} from '../../../src/render3/instructions'; import {createLView, createTView, elementClassProp, elementEnd, elementStart, elementStyleProp, elementStyling, elementStylingApply, elementStylingMap} from '../../../src/render3/instructions';
import {InitialStylingFlags, RenderFlags} from '../../../src/render3/interfaces/definition'; import {InitialStylingFlags, RenderFlags} from '../../../src/render3/interfaces/definition';
@ -2264,7 +2264,7 @@ describe('style and class based bindings', () => {
fixture.update(); fixture.update();
const target = fixture.hostElement.querySelector('div') !as any; const target = fixture.hostElement.querySelector('div') !as any;
const elementContext = getContext(target) !; const elementContext = getLContext(target) !;
const context = elementContext.lView[elementContext.nodeIndex] as StylingContext; const context = elementContext.lView[elementContext.nodeIndex] as StylingContext;
expect(players.length).toEqual(4); expect(players.length).toEqual(4);

View File

@ -1,11 +1,15 @@
export declare function getComponent<T = {}>(element: Element): T | null; export declare function getComponent<T = {}>(element: Element): T | null;
export declare function getContext<T = {}>(element: Element): T | null;
export declare function getDirectives(target: {}): Array<{}>; export declare function getDirectives(target: {}): Array<{}>;
export declare function getHostElement<T>(directive: T): Element; export declare function getHostElement<T>(directive: T): Element;
export declare function getInjector(target: {}): Injector; export declare function getInjector(target: {}): Injector;
export declare function getListeners(element: Element): Listener[];
export declare function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[]; export declare function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[];
export declare function getRootComponents(target: {}): any[]; export declare function getRootComponents(target: {}): any[];