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';
export {
getContext as ɵgetContext
getLContext as ɵgetLContext
} from './render3/context_discovery';
export {

View File

@ -7,11 +7,13 @@
*/
import {Injector} from '../di';
import {DirectiveDef} from '../render3';
import {assertDomNode} from '../render3/assert';
import {getComponent, getInjector, getLocalRefs, loadContext} from '../render3/discovery_utils';
import {TNode, TNodeFlags} from '../render3/interfaces/node';
import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext, loadLContextFromNode} from '../render3/discovery_utils';
import {TNode} from '../render3/interfaces/node';
import {StylingIndex} from '../render3/interfaces/styling';
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';
export class EventListener {
@ -204,7 +206,7 @@ class DebugNode__POST_R3__ implements DebugNode {
constructor(nativeNode: Node) { this.nativeNode = nativeNode; }
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;
}
@ -212,46 +214,17 @@ class DebugNode__POST_R3__ implements DebugNode {
get componentInstance(): any {
const nativeElement = this.nativeNode;
return nativeElement && getComponent(nativeElement as HTMLElement);
}
get context(): any {
// https://angular-team.atlassian.net/browse/FW-719
throw notImplemented();
return nativeElement && getComponent(nativeElement as Element);
}
get context(): any { return getContext(this.nativeNode as Element); }
get listeners(): EventListener[] {
// TODO: add real implementation;
// https://angular-team.atlassian.net/browse/FW-719
return [];
return getListeners(this.nativeNode as Element).filter(isBrowserEvents);
}
get references(): {[key: string]: any;} { return getLocalRefs(this.nativeNode); }
get providerTokens(): any[] {
// 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;
}
get providerTokens(): any[] { return getInjectionTokens(this.nativeNode as Element); }
}
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;
}
get name(): string { return (this.nativeElement as HTMLElement).nodeName; }
get name(): string { return this.nativeElement !.nodeName; }
get properties(): {[key: string]: any;} {
const context = loadContext(this.nativeNode) !;
const context = loadLContext(this.nativeNode) !;
const lView = context.lView;
const tView = lView[TVIEW];
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;} {
// https://angular-team.atlassian.net/browse/FW-719
throw notImplemented();
const attributes: {[key: string]: string | null;} = {};
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;} {
// https://angular-team.atlassian.net/browse/FW-719
throw notImplemented();
const classes: {[key: string]: boolean;} = {};
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;} {
// https://angular-team.atlassian.net/browse/FW-719
throw notImplemented();
const styles: {[key: string]: string | null;} = {};
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[] {
@ -332,24 +364,14 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
}
triggerEventHandler(eventName: string, eventObj: any): void {
// This is a hack implementation. The correct implementation would bypass the DOM and `TNode`
// information to invoke the listeners directly.
// https://angular-team.atlassian.net/browse/FW-719
const event = document.createEvent('MouseEvent');
event.initEvent(eventName, true, true);
(this.nativeElement as HTMLElement).dispatchEvent(event);
this.listeners.forEach((listener) => {
if (listener.name === eventName) {
listener.callback(eventObj);
}
});
}
}
/**
* 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(
parentNode: DebugNode, predicate: Predicate<DebugNode>, matches: DebugNode[],
elementsOnly: boolean) {

View File

@ -17,12 +17,12 @@ import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {publishDefaultGlobalUtils} from './global_utils';
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 {TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player';
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 {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;
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.
*/
export function getContext(target: any): LContext|null {
export function getLContext(target: any): LContext|null {
let mpValue = readPatchedData(target);
if (mpValue) {
// 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.
let tNode = lView[TVIEW].firstChild;
while (tNode) {
const directiveIndexStart = getDirectiveStartIndex(tNode);
const directiveIndexEnd = getDirectiveEndIndex(tNode, directiveIndexStart);
const directiveIndexStart = tNode.directiveStart;
const directiveIndexEnd = tNode.directiveEnd;
for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
if (lView[i] === directiveInstance) {
return tNode.index;
@ -273,16 +273,16 @@ function findViaDirective(lView: LView, directiveInstance: {}): number {
export function getDirectivesAtNodeIndex(
nodeIndex: number, lView: LView, includeComponents: boolean): any[]|null {
const tNode = lView[TVIEW].data[nodeIndex] as TNode;
let directiveStartIndex = getDirectiveStartIndex(tNode);
let directiveStartIndex = tNode.directiveStart;
if (directiveStartIndex == 0) return EMPTY_ARRAY;
const directiveEndIndex = getDirectiveEndIndex(tNode, directiveStartIndex);
const directiveEndIndex = tNode.directiveEnd;
if (!includeComponents && tNode.flags & TNodeFlags.isComponent) directiveStartIndex++;
return lView.slice(directiveStartIndex, directiveEndIndex);
}
export function getComponentAtNodeIndex(nodeIndex: number, lView: LView): {}|null {
const tNode = lView[TVIEW].data[nodeIndex] as TNode;
let directiveStartIndex = getDirectiveStartIndex(tNode);
let directiveStartIndex = tNode.directiveStart;
return tNode.flags & TNodeFlags.isComponent ? lView[directiveStartIndex] : null;
}
@ -305,18 +305,3 @@ export function discoverLocalRefs(lView: LView, nodeIndex: number): {[key: strin
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 = {};
function searchTokensOnInjector<T>(
injectorIndex: number, injectorView: LView, token: Type<T>| InjectionToken<T>,
injectorIndex: number, lView: LView, token: Type<T>| InjectionToken<T>,
previousTView: TView | null) {
const currentTView = injectorView[TVIEW];
const currentTView = lView[TVIEW];
const tNode = currentTView.data[injectorIndex + TNODE] as TNode;
// First, we step through providers
let canAccessViewProviders = false;
// We need to determine if view providers can be accessed by the starting element.
// It happens in 2 cases:
// 1) On the initial element injector , if we are instantiating a token which can see the
// viewProviders of the component of that element. Such token are:
// - the component itself (but not other directives)
// - viewProviders tokens of the component (but not providers tokens)
// 2) Upper in the element injector tree, if the starting element is actually in the view of
// the current element. To determine this, we track the transition of view during the climb,
// and check the host node of the current view to identify component views.
if (previousTView == null && isComponent(tNode) && includeViewProviders ||
previousTView != null && previousTView != currentTView &&
(currentTView.node == null || currentTView.node.type === TNodeType.Element)) {
canAccessViewProviders = true;
}
const injectableIdx =
locateDirectiveOrProvider(tNode, injectorView, token, canAccessViewProviders);
// First, we need to determine if view providers can be accessed by the starting element.
// There are two possibities
const canAccessViewProviders = previousTView == null ?
// 1) This is the first invocation `previousTView == null` which means that we are at the
// `TNode` of where injector is starting to look. In such a case the only time we are allowed
// to look into the ViewProviders is if:
// - we are on a component
// - AND the injector set `includeViewProviders` to true (implying that the token can see
// ViewProviders because it is the Component or a Service which itself was declared in
// ViewProviders)
(isComponent(tNode) && includeViewProviders) :
// 2) `previousTView != null` which means that we are now walking across the parent nodes.
// In such a case we are only allowed to look into the ViewProviders if:
// - We just crossed from child View to Parent View `previousTView != currentTView`
// - AND the parent TNode is an Element.
// This means that we just came from the Component's View and therefore are allowed to see
// into the ViewProviders.
(previousTView != currentTView && (tNode.type === TNodeType.Element));
const injectableIdx = locateDirectiveOrProvider(tNode, lView, token, canAccessViewProviders);
if (injectableIdx !== null) {
return getNodeInjectable(currentTView.data, injectorView, injectableIdx, tNode as TElementNode);
return getNodeInjectable(currentTView.data, lView, injectableIdx, tNode as TElementNode);
} else {
return NOT_FOUND;
}
@ -439,17 +441,17 @@ export function locateDirectiveOrProvider<T>(
const nodeProviderIndexes = tNode.providerIndexes;
const tInjectables = tView.data;
const startInjectables = nodeProviderIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const startDirectives = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
const injectablesStart = nodeProviderIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const directivesStart = tNode.directiveStart;
const directiveEnd = tNode.directiveEnd;
const cptViewProvidersCount =
nodeProviderIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
const startingIndex =
canAccessViewProviders ? startInjectables : startInjectables + cptViewProvidersCount;
const directiveCount = nodeFlags & TNodeFlags.DirectiveCountMask;
for (let i = startingIndex; i < startDirectives + directiveCount; i++) {
canAccessViewProviders ? injectablesStart : injectablesStart + cptViewProvidersCount;
for (let i = startingIndex; i < directiveEnd; i++) {
const providerTokenOrDef = tInjectables[i] as InjectionToken<any>| Type<any>| DirectiveDef<any>;
if (i < startDirectives && token === providerTokenOrDef ||
i >= startDirectives && (providerTokenOrDef as DirectiveDef<any>).type === token) {
if (i < directivesStart && token === providerTokenOrDef ||
i >= directivesStart && (providerTokenOrDef as DirectiveDef<any>).type === token) {
return i;
}
}

View File

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

View File

@ -8,10 +8,12 @@
import {Injector} from '../di/injector';
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 {TElementNode} from './interfaces/node';
import {CONTEXT, FLAGS, HOST, LView, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
import {DirectiveDef} from './interfaces/definition';
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 {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.
* 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>
@ -37,9 +39,7 @@ import {NodeInjector} from './view_engine_compatibility';
* @publicApi
*/
export function getComponent<T = {}>(element: Element): T|null {
if (!(element instanceof Node)) throw new Error('Expecting instance of DOM Node');
const context = loadContext(element) !;
const context = loadLContextFromNode(element);
if (context.component === undefined) {
context.component = getComponentAtNodeIndex(context.nodeIndex, context.lView);
@ -48,6 +48,31 @@ export function getComponent<T = {}>(element: Element): T|null {
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`
* otherwise).
@ -69,7 +94,7 @@ export function getComponent<T = {}>(element: Element): T|null {
* @publicApi
*/
export function getViewComponent<T = {}>(element: Element | {}): T|null {
const context = loadContext(element) !;
const context = loadLContext(element) !;
let lView: LView = context.lView;
while (lView[PARENT] && lView[HOST] === null) {
// 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 {
const lView = Array.isArray(target) ? target : loadContext(target) !.lView;
const rootLView = getRootView(lView);
const lViewData = Array.isArray(target) ? target : loadLContext(target) !.lView;
const rootLView = getRootView(lViewData);
return rootLView[CONTEXT] as RootContext;
}
@ -113,12 +138,40 @@ export function getRootComponents(target: {}): any[] {
* @publicApi
*/
export function getInjector(target: {}): Injector {
const context = loadContext(target);
const context = loadLContext(target);
const tNode = context.lView[TVIEW].data[context.nodeIndex] as TElementNode;
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.
*
@ -127,7 +180,7 @@ export function getInjector(target: {}): Injector {
* @publicApi
*/
export function getDirectives(target: {}): Array<{}> {
const context = loadContext(target) !;
const context = loadLContext(target) !;
if (context.directives === undefined) {
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.
*
*/
export function loadContext(target: {}): LContext;
export function loadContext(target: {}, throwOnNotFound: false): LContext|null;
export function loadContext(target: {}, throwOnNotFound: boolean = true): LContext|null {
const context = getContext(target);
export function loadLContext(target: {}): LContext;
export function loadLContext(target: {}, throwOnNotFound: false): LContext|null;
export function loadLContext(target: {}, throwOnNotFound: boolean = true): LContext|null {
const context = getLContext(target);
if (!context && throwOnNotFound) {
throw new Error(
ngDevMode ? `Unable to find context associated with ${stringify(target)}` :
@ -185,7 +238,7 @@ export function getRootView(componentOrView: LView | {}): LView {
* @publicApi
*/
export function getLocalRefs(target: {}): {[key: string]: any} {
const context = loadContext(target) !;
const context = loadLContext(target) !;
if (context.localRefs === undefined) {
context.localRefs = discoverLocalRefs(context.lView, context.nodeIndex);
@ -205,7 +258,7 @@ export function getLocalRefs(target: {}): {[key: string]: any} {
* @publicApi
*/
export function getHostElement<T>(directive: T): Element {
return getContext(directive) !.native as never as Element;
return getLContext(directive) !.native as never as Element;
}
/**
@ -222,3 +275,88 @@ export function getRenderedText(component: any): string {
const hostElement = getHostElement(component);
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 {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) {
_published = true;
publishGlobalUtil('getComponent', getComponent);
publishGlobalUtil('getContext', getContext);
publishGlobalUtil('getListeners', getListeners);
publishGlobalUtil('getViewComponent', getViewComponent);
publishGlobalUtil('getHostElement', getHostElement);
publishGlobalUtil('getInjector', getInjector);

View File

@ -15,6 +15,6 @@
* 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 {getPlayers} from './players';

View File

@ -8,10 +8,11 @@
import {assertEqual} from './assert';
import {DirectiveDef} from './interfaces/definition';
import {TNodeFlags} from './interfaces/node';
import {TNode} from './interfaces/node';
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
* 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
* 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) {
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
// 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.
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>;
queueContentHooks(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 {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
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 {PlayerFactory} from './interfaces/player';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
@ -112,7 +112,7 @@ export function setHostBindings(tView: TView, viewData: LView): void {
currentElementIndex = -instruction;
// Injector block and providers are taken into account.
const providerCount = (tView.expandoInstructions[++i] as number);
bindingRootIndex += INJECTOR_SIZE + providerCount;
bindingRootIndex += INJECTOR_BLOOM_PARENT_SIZE + providerCount;
currentDirectiveIndex = bindingRootIndex;
} else {
@ -215,6 +215,7 @@ export function createNodeAtIndex(
if (tNode == null) {
const previousOrParentTNode = getPreviousOrParentTNode();
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);
// 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;
}
setIsParent(true);
const tNode = view[TVIEW].node as TViewNode;
setPreviousOrParentTNode(tNode);
return view[HOST_NODE] = tNode;
return view[HOST_NODE] = view[TVIEW].node as TViewNode;
}
@ -526,7 +524,7 @@ export function elementContainerEnd(): void {
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 {
const lView = getLView();
const tNode = getPreviousOrParentTNode();
const tView = lView[TVIEW];
const firstTemplatePass = tView.firstTemplatePass;
const tCleanup: false|any[] = firstTemplatePass && (tView.cleanup || (tView.cleanup = []));
ngDevMode && assertNodeOfPossibleTypes(
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
@ -808,47 +809,45 @@ export function listener(
const native = getNativeByTNode(tNode, lView) as RElement;
ngDevMode && ngDevMode.rendererAddEventListener++;
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
// events (including outputs).
if (isProceduralRenderer(renderer)) {
const cleanupFn = renderer.listen(native, eventName, listenerFn);
storeCleanupFn(lView, cleanupFn);
lCleanup.push(listenerFn, cleanupFn);
useCaptureOrSubIdx = lCleanupIndex + 1;
} else {
const wrappedListener = wrapListenerWithPreventDefault(listenerFn);
native.addEventListener(eventName, wrappedListener, useCapture);
const cleanupInstances = getCleanup(lView);
cleanupInstances.push(wrappedListener);
if (getFirstTemplatePass()) {
getTViewCleanup(lView).push(
eventName, tNode.index, cleanupInstances !.length - 1, useCapture);
}
lCleanup.push(wrappedListener);
}
tCleanup && tCleanup.push(eventName, tNode.index, lCleanupIndex, useCaptureOrSubIdx);
}
// subscribe to directive outputs
if (tNode.outputs === undefined) {
// if we create TNode here, inputs must be undefined so we know they still need to be
// checked
tNode.outputs = generatePropertyAliases(tNode.flags, BindingDirection.Output);
tNode.outputs = generatePropertyAliases(tNode, BindingDirection.Output);
}
const outputs = tNode.outputs;
let outputData: PropertyAliasValue|undefined;
if (outputs && (outputData = outputs[eventName])) {
createOutput(lView, outputData, listenerFn);
}
}
/**
* Iterates through the outputs associated with a particular event name and subscribes to
* each output.
*/
function createOutput(lView: LView, outputs: PropertyAliasValue, listener: Function): void {
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);
let props: PropertyAliasValue|undefined;
if (outputs && (props = outputs[eventName])) {
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);
const subscription = lView[props[i] as number][props[i + 1]].subscribe(listenerFn);
const idx = lCleanup.length;
lCleanup.push(listenerFn, subscription);
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
}
}
}
}
@ -860,10 +859,11 @@ function createOutput(lView: LView, outputs: PropertyAliasValue, listener: Funct
* - Index of context we just saved in LView.cleanupInstances
*/
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) {
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);
}
queueLifecycleHooks(previousOrParentTNode.flags, getLView()[TVIEW]);
queueLifecycleHooks(getLView()[TVIEW], previousOrParentTNode);
decreaseElementDepthCount();
}
@ -984,7 +984,7 @@ export function elementProperty<T>(
* @returns the TNode object
*/
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 {
const previousOrParentTNode = getPreviousOrParentTNode();
ngDevMode && ngDevMode.tNode++;
@ -993,13 +993,15 @@ export function createTNode(
// Parents cannot cross component boundaries because components will be used in multiple places,
// 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;
return {
type: type,
index: adjustedIndex,
injectorIndex: tParent ? tParent.injectorIndex : -1,
directiveStart: -1,
directiveEnd: -1,
flags: 0,
providerIndexes: 0,
tagName: tagName,
@ -1044,15 +1046,13 @@ function setNgReflectProperties(lView: LView, element: RElement, propName: strin
* @param Direction direction whether to consider inputs or outputs
* @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise
*/
function generatePropertyAliases(
tNodeFlags: TNodeFlags, direction: BindingDirection): PropertyAliases|null {
function generatePropertyAliases(tNode: TNode, direction: BindingDirection): PropertyAliases|null {
const tView = getLView()[TVIEW];
const count = tNodeFlags & TNodeFlags.DirectiveCountMask;
let propStore: PropertyAliases|null = null;
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
if (count > 0) {
const start = tNodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
const end = start + count;
if (end > start) {
const isInput = direction === BindingDirection.Input;
const defs = tView.data;
@ -1093,7 +1093,7 @@ export function elementClassProp(
}
const val =
(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 ||
classDeclarations && classDeclarations.length) {
const index = tNode.index - HEADER_OFFSET;
const index = tNode.index;
if (delegateToClassInput(tNode)) {
const lView = getLView();
const stylingContext = getStylingContext(index, lView);
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
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 isFirstRender = (lView[FLAGS] & LViewFlags.CreationMode) !== 0;
const totalPlayersQueued = renderStyleAndClassBindings(
getStylingContext(index, lView), lView[RENDERER], lView, isFirstRender);
getStylingContext(index + HEADER_OFFSET, lView), lView[RENDERER], lView, isFirstRender);
if (totalPlayersQueued > 0) {
const rootContext = getRootContext(lView);
scheduleTick(rootContext, RootContextFlags.FlushPlayers);
@ -1234,7 +1234,8 @@ export function elementStyleProp(
if (directive != undefined) {
hackImplementationOfElementStyleProp(index, styleIndex, valueToAdd, suffix, directive);
} 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
const lView = getLView();
const tNode = getTNode(index, lView);
const stylingContext = getStylingContext(index, lView);
const stylingContext = getStylingContext(index + HEADER_OFFSET, lView);
if (delegateToClassInput(tNode) && classes !== NO_CHANGE) {
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
const classInputVal =
@ -1482,27 +1483,26 @@ function resolveDirectives(
/**
* Instantiate all the directives that were previously resolved on the current node.
*/
function instantiateAllDirectives(tView: TView, viewData: LView, previousOrParentTNode: TNode) {
const start = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
const end = start + (previousOrParentTNode.flags & TNodeFlags.DirectiveCountMask);
function instantiateAllDirectives(tView: TView, lView: LView, tNode: TNode) {
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
if (!getFirstTemplatePass() && start < end) {
getOrCreateNodeInjectorForNode(
previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode, viewData);
tNode as TElementNode | TContainerNode | TElementContainerNode, lView);
}
for (let i = start; i < end; i++) {
const def = tView.data[i] as DirectiveDef<any>;
if (isComponentDef(def)) {
addComponentLogic(viewData, previousOrParentTNode, def as ComponentDef<any>);
addComponentLogic(lView, tNode, def as ComponentDef<any>);
}
const directive =
getNodeInjectable(tView.data, viewData !, i, previousOrParentTNode as TElementNode);
postProcessDirective(viewData, directive, def, i);
const directive = getNodeInjectable(tView.data, lView !, i, tNode as TElementNode);
postProcessDirective(lView, directive, def, i);
}
}
function invokeDirectivesHostBindings(tView: TView, viewData: LView, previousOrParentTNode: TNode) {
const start = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
const end = start + (previousOrParentTNode.flags & TNodeFlags.DirectiveCountMask);
function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNode) {
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
const expando = tView.expandoInstructions !;
const firstTemplatePass = getFirstTemplatePass();
for (let i = start; i < end; i++) {
@ -1511,7 +1511,7 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, previousOrP
if (def.hostBindings) {
const previousExpandoLength = expando.length;
setCurrentDirectiveDef(def);
def.hostBindings !(RenderFlags.Create, directive, previousOrParentTNode.index);
def.hostBindings !(RenderFlags.Create, directive, tNode.index);
setCurrentDirectiveDef(null);
// `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
@ -1715,11 +1715,12 @@ export function initNodeFlags(tNode: TNode, index: number, numberOfDirectives: n
'expected node flags to not be initialized');
ngDevMode && assertNotEqual(
numberOfDirectives, TNodeFlags.DirectiveCountMask,
numberOfDirectives, tNode.directiveEnd - tNode.directiveStart,
'Reached the max number of directives');
// When the first directive is created on a node, save the index
tNode.flags = index << TNodeFlags.DirectiveStartingIndexShift | flags & TNodeFlags.isComponent |
numberOfDirectives;
tNode.flags = flags & TNodeFlags.isComponent;
tNode.directiveStart = index;
tNode.directiveEnd = index + numberOfDirectives;
tNode.providerIndexes = index;
}
@ -1894,7 +1895,7 @@ export function template(
if (currentQueries) {
lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TContainerNode);
}
queueLifecycleHooks(tNode.flags, tView);
queueLifecycleHooks(tView, tNode);
setIsParent(false);
}
@ -2842,7 +2843,7 @@ function initializeTNodeInputs(tNode: TNode | null) {
if (tNode) {
if (tNode.inputs === undefined) {
// mark inputs as checked
tNode.inputs = generatePropertyAliases(tNode.flags, BindingDirection.Input);
tNode.inputs = generatePropertyAliases(tNode, BindingDirection.Input);
}
return tNode.inputs;
}

View File

@ -14,7 +14,7 @@ import {LView, TData} from './view';
export const TNODE = 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.

View File

@ -28,23 +28,17 @@ export const enum TNodeType {
* Corresponds to the TNode.flags property.
*/
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 */
isComponent = 0b00000000000000000001000000000000,
isComponent = 0b0001,
/** 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 */
hasContentQuery = 0b00000000000000000100000000000000,
hasContentQuery = 0b0100,
/** This bit is set if the node has any directives that contain [class properties */
hasClassInput = 0b00000000000000001000000000000000,
/** The index of the first directive on this node is encoded on the most significant bits */
DirectiveStartingIndexShift = 16,
hasClassInput = 0b1000,
}
/**
@ -128,13 +122,17 @@ export interface TNode {
injectorIndex: number;
/**
* This number stores two values using its bits:
*
* - the number of directives on that node (first 12 bits)
* - 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
* with a node without searching the whole directives array.
* Stores starting index of the directives.
*/
directiveStart: number;
/**
* Stores final exclusive index of the directives.
*/
directiveEnd: number;
/**
* Stores if Node isComponent, isProjected, hasContentQuery and hasClassInput
*/
flags: TNodeFlags;
@ -144,6 +142,7 @@ export interface TNode {
* - 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)
*/
// TODO(misko): break this into actual vars.
providerIndexes: TNodeProviderIndexes;
/** 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
* separate for loops).
*
* If it's a native DOM listener being stored:
* 1st index is: event name to remove
* 2nd index is: index of native element in LView.data[]
* 3rd index is: index of wrapped listener function in LView.cleanupInstances[]
* 4th index is: useCapture boolean
* If it's a native DOM listener or output subscription being stored:
* 1st index is: event name `name = tView.cleanup[i+0]`
* 2nd index is: index of native element `element = lView[tView.cleanup[i+1]]`
* 3rd index is: index of listener function `listener = lView[CLEANUP][tView.cleanup[i+2]]`
* 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:
* 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:
* 1st index is: output unsubscribe function / query list destroy function
* 2nd index is: index of function context in LView.cleanupInstances[]
* `tView.cleanup[i+0].call(lView[CLEANUP][tView.cleanup[i+1]])`
*/
cleanup: any[]|null;

View File

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

View File

@ -7,7 +7,7 @@
*/
import './ng_dev_mode';
import {getContext} from './context_discovery';
import {getLContext} from './context_discovery';
import {getRootContext} from './discovery_utils';
import {scheduleTick} from './instructions';
import {ComponentInstance, DirectiveInstance, Player} from './interfaces/player';
@ -29,7 +29,7 @@ import {addPlayerInternal, getOrCreatePlayerContext, getPlayerContext, getPlayer
*/
export function addPlayer(
ref: ComponentInstance | DirectiveInstance | HTMLElement, player: Player): void {
const context = getContext(ref);
const context = getLContext(ref);
if (!context) {
ngDevMode && throwInvalidRefError();
return;
@ -54,13 +54,13 @@ export function addPlayer(
* @publicApi
*/
export function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[] {
const context = getContext(ref);
const context = getLContext(ref);
if (!context) {
ngDevMode && throwInvalidRefError();
return [];
}
const stylingContext = getStylingContext(context.nodeIndex - HEADER_OFFSET, context.lView);
const stylingContext = getStylingContext(context.nodeIndex, context.lView);
const playerContext = stylingContext ? getPlayerContext(stylingContext) : null;
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;
}
function isClassBased(context: StylingContext, index: number): boolean {
export function isClassBased(context: StylingContext, index: number): boolean {
const adjustedIndex =
index >= StylingIndex.SingleStylesStartPosition ? (index + StylingIndex.FlagsOffset) : index;
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;
}
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;
}
function getProp(context: StylingContext, index: number): string {
export function getProp(context: StylingContext, index: number): string {
return context[index + StylingIndex.PropertyOffset] as string;
}

View File

@ -8,7 +8,7 @@
import '../ng_dev_mode';
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {getContext} from '../context_discovery';
import {getLContext} from '../context_discovery';
import {ACTIVE_INDEX, LContainer} from '../interfaces/container';
import {LContext} from '../interfaces/context';
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
*/
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 wrapper: LContainer|LView|StylingContext = viewData;
@ -73,7 +73,7 @@ export function getStylingContext(index: number, viewData: LView): StylingContex
return wrapper as StylingContext;
} else {
// This is an LView or an LContainer
const stylingTemplate = getTNode(index, viewData).stylingTemplate;
const stylingTemplate = getTNode(index - HEADER_OFFSET, viewData).stylingTemplate;
if (wrapper !== viewData) {
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
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(
@ -152,14 +153,14 @@ export function getPlayersInternal(playerContext: PlayerContext): Player[] {
export function getOrCreatePlayerContext(target: {}, context?: LContext | null): PlayerContext|
null {
context = context || getContext(target) !;
context = context || getLContext(target) !;
if (!context) {
ngDevMode && throwInvalidRefError();
return null;
}
const {lView, nodeIndex} = context;
const stylingContext = getStylingContext(nodeIndex - HEADER_OFFSET, lView);
const stylingContext = getStylingContext(nodeIndex, lView);
return getPlayerContext(stylingContext) || allocPlayerContext(stylingContext);
}

View File

@ -8,7 +8,7 @@
import {global} from '../util';
import {assertDataInRange, assertDefined} from './assert';
import {assertDataInRange, assertDefined, assertGreaterThan, assertLessThan} from './assert';
import {ACTIVE_INDEX, LContainer} from './interfaces/container';
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
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 {
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;
}
@ -157,6 +159,7 @@ export function getRootContext(viewOrComponent: LView | {}): RootContext {
* a component, directive or a DOM node).
*/
export function readPatchedData(target: any): LView|LContext|null {
ngDevMode && assertDefined(target, 'Target expected');
return target[MONKEY_PATCH_KEY_NAME];
}

View File

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

View File

@ -12,7 +12,7 @@ import {stringify} from '../render3/util';
import {BypassType, allowSanitizationBypass} from './bypass';
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 {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
@ -32,7 +32,7 @@ import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
* and urls have been removed.
*/
export function sanitizeHtml(unsafeHtml: any): string {
const sanitizer = getLView()[SANITIZER];
const sanitizer = getSanitizer();
if (sanitizer) {
return sanitizer.sanitize(SecurityContext.HTML, unsafeHtml) || '';
}
@ -56,7 +56,7 @@ export function sanitizeHtml(unsafeHtml: any): string {
* dangerous javascript and urls have been removed.
*/
export function sanitizeStyle(unsafeStyle: any): string {
const sanitizer = getLView()[SANITIZER];
const sanitizer = getSanitizer();
if (sanitizer) {
return sanitizer.sanitize(SecurityContext.STYLE, unsafeStyle) || '';
}
@ -81,7 +81,7 @@ export function sanitizeStyle(unsafeStyle: any): string {
* all of the dangerous javascript has been removed.
*/
export function sanitizeUrl(unsafeUrl: any): string {
const sanitizer = getLView()[SANITIZER];
const sanitizer = getSanitizer();
if (sanitizer) {
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.
*/
export function sanitizeResourceUrl(unsafeResourceUrl: any): string {
const sanitizer = getLView()[SANITIZER];
const sanitizer = getSanitizer();
if (sanitizer) {
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.
*/
export function sanitizeScript(unsafeScript: any): string {
const sanitizer = getLView()[SANITIZER];
const sanitizer = getSanitizer();
if (sanitizer) {
return sanitizer.sanitize(SecurityContext.SCRIPT, unsafeScript) || '';
}
@ -145,3 +145,8 @@ export const defaultStyleSanitizer = (function(prop: string, value?: string): st
return sanitizeStyle(value);
} as StyleSanitizeFn);
function getSanitizer(): Sanitizer|null {
const lView = getLView();
return lView && lView[SANITIZER];
}

View File

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

View File

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

View File

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

View File

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

View File

@ -381,7 +381,7 @@
"name": "INJECTOR$1"
},
{
"name": "INJECTOR_SIZE"
"name": "INJECTOR_BLOOM_PARENT_SIZE"
},
{
"name": "INSPECT_GLOBAL_NAME"
@ -1379,9 +1379,6 @@
{
"name": "createNodeAtIndex"
},
{
"name": "createOutput"
},
{
"name": "createPlatform"
},
@ -1736,12 +1733,6 @@
{
"name": "getDirectiveDef"
},
{
"name": "getDirectiveEndIndex"
},
{
"name": "getDirectiveStartIndex"
},
{
"name": "getDirectivesAtNodeIndex"
},
@ -1775,6 +1766,9 @@
{
"name": "getInjectableDef"
},
{
"name": "getInjectionTokens"
},
{
"name": "getInjector"
},
@ -1790,6 +1784,9 @@
{
"name": "getLContainer"
},
{
"name": "getLContext"
},
{
"name": "getLView"
},
@ -1799,6 +1796,9 @@
{
"name": "getLastDefinedValue"
},
{
"name": "getListeners"
},
{
"name": "getLocalRefs"
},
@ -2120,6 +2120,12 @@
{
"name": "isBlackListedEvent"
},
{
"name": "isBrowserEvents"
},
{
"name": "isClassBased"
},
{
"name": "isComponent"
},
@ -2256,10 +2262,13 @@
"name": "listener"
},
{
"name": "loadContext"
"name": "loadInternal"
},
{
"name": "loadInternal"
"name": "loadLContext"
},
{
"name": "loadLContextFromNode"
},
{
"name": "localeEn"
@ -2348,9 +2357,6 @@
{
"name": "noopScope"
},
{
"name": "notImplemented"
},
{
"name": "observable"
},
@ -2621,6 +2627,9 @@
{
"name": "shouldSearchParent"
},
{
"name": "sortListeners"
},
{
"name": "staticError"
},
@ -2633,9 +2642,6 @@
{
"name": "storeCleanupFn"
},
{
"name": "storeCleanupWithContext"
},
{
"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 {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {fixmeIvy} from '@angular/private/testing';
@Injectable()
class Logger {
@ -259,35 +258,32 @@ class TestApp {
expect(list.children.length).toEqual(3);
});
fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') &&
it('should list element attributes', () => {
fixture = TestBed.createComponent(TestApp);
fixture.detectChanges();
const bankElem = fixture.debugElement.children[0];
it('should list element attributes', () => {
fixture = TestBed.createComponent(TestApp);
fixture.detectChanges();
const bankElem = fixture.debugElement.children[0];
expect(bankElem.attributes['bank']).toEqual('RBC');
expect(bankElem.attributes['account']).toEqual('4747');
});
expect(bankElem.attributes['bank']).toEqual('RBC');
expect(bankElem.attributes['account']).toEqual('4747');
});
fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') &&
it('should list element classes', () => {
fixture = TestBed.createComponent(TestApp);
fixture.detectChanges();
const bankElem = fixture.debugElement.children[0];
it('should list element classes', () => {
fixture = TestBed.createComponent(TestApp);
fixture.detectChanges();
const bankElem = fixture.debugElement.children[0];
expect(bankElem.classes['closed']).toBe(true);
expect(bankElem.classes['open']).toBe(false);
});
expect(bankElem.classes['closed']).toBe(true);
expect(bankElem.classes['open']).toBe(false);
});
fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') &&
it('should list element styles', () => {
fixture = TestBed.createComponent(TestApp);
fixture.detectChanges();
const bankElem = fixture.debugElement.children[0];
it('should list element styles', () => {
fixture = TestBed.createComponent(TestApp);
fixture.detectChanges();
const bankElem = fixture.debugElement.children[0];
expect(bankElem.styles['width']).toEqual('200px');
expect(bankElem.styles['color']).toEqual('red');
});
expect(bankElem.styles['width']).toEqual('200px');
expect(bankElem.styles['color']).toEqual('red');
});
it('should query child elements by css', () => {
fixture = TestBed.createComponent(ParentComp);
@ -312,13 +308,12 @@ class TestApp {
expect(getDOM().hasClass(childTestEls[3].nativeElement, 'childnested')).toBe(true);
});
fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') &&
it('should list providerTokens', () => {
fixture = TestBed.createComponent(ParentComp);
fixture.detectChanges();
it('should list providerTokens', () => {
fixture = TestBed.createComponent(ParentComp);
fixture.detectChanges();
expect(fixture.debugElement.providerTokens).toContain(Logger);
});
expect(fixture.debugElement.providerTokens).toContain(Logger);
});
it('should list locals', () => {
fixture = TestBed.createComponent(LocalsComp);
@ -327,25 +322,22 @@ class TestApp {
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', () => {
fixture = TestBed.createComponent(ParentComp);
fixture.detectChanges();
it('should allow injecting from the element injector', () => {
fixture = TestBed.createComponent(ParentComp);
fixture.detectChanges();
expect((<Logger>(fixture.debugElement.children[0].injector.get(Logger))).logs).toEqual([
'parent', 'nestedparent', 'child', 'nestedchild'
]);
});
expect((<Logger>(fixture.debugElement.children[0].injector.get(Logger))).logs).toEqual([
'parent', 'nestedparent', 'child', 'nestedchild'
]);
});
fixmeIvy('FW-719: DebugElement needs proper implementation of its methods.') &&
it('should list event listeners', () => {
fixture = TestBed.createComponent(EventsComp);
fixture.detectChanges();
it('should list event listeners', () => {
fixture = TestBed.createComponent(EventsComp);
fixture.detectChanges();
expect(fixture.debugElement.children[0].listeners.length).toEqual(1);
expect(fixture.debugElement.children[1].listeners.length).toEqual(1);
});
expect(fixture.debugElement.children[0].listeners.length).toEqual(1);
expect(fixture.debugElement.children[1].listeners.length).toEqual(1);
});
it('should trigger event handlers', () => {
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
* found in the LICENSE file at https://angular.io/license
*/
import {ReferenceFilter} from '@angular/compiler';
import {createInjector} from '@angular/core';
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 {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 {NgIf} from './common_with_def';
import {getRootContext} from '@angular/core/src/render3/util';
describe('discovery utils', () => {
let fixture: ComponentFixture<MyApp>;
@ -27,8 +25,10 @@ describe('discovery utils', () => {
let span: NodeListOf<Element>;
let div: NodeListOf<Element>;
let p: NodeListOf<Element>;
let log: any[];
beforeEach(() => {
log = [];
myApp = [];
dirA = [];
childComponent = [];
@ -46,7 +46,7 @@ describe('discovery utils', () => {
* ```
* <my-app>
* <#VIEW>
* <span>{{text}}</span>
* <span (click)=" log.push($event) ">{{text}}</span>
* <div dirA #div #foo="dirA"></div>
* <child>
* <#VIEW>
@ -110,6 +110,7 @@ describe('discovery utils', () => {
template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
listener('click', $event => log.push($event));
text(1);
elementEnd();
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', () => {
it('should return element on component', () => {
expect(getHostElement(myApp[0])).toEqual(fixture.hostElement);
@ -217,7 +230,7 @@ describe('discovery utils', () => {
});
});
describe('localRefs', () => {
describe('getLocalRefs', () => {
it('should retrieve empty map', () => {
expect(getLocalRefs(fixture.hostElement)).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', () => {
it('should re-render component', () => {
expect(span[0].textContent).toEqual('INIT');

View File

@ -7,7 +7,7 @@
*/
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 {getPlayers} from '../../src/render3/players';
import {global} from '../../src/util';
@ -28,6 +28,10 @@ describe('global utils', () => {
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',
() => { assertPublished('getViewComponent', getViewComponent); });

View File

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

View File

@ -21,7 +21,7 @@ import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
import {NgIf} from './common_with_def';
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 {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context';
@ -2041,14 +2041,14 @@ describe('render3 integration test', () => {
fixture.update();
const section = fixture.hostElement.querySelector('section') !;
const sectionContext = getContext(section) !;
const sectionContext = getLContext(section) !;
const sectionLView = sectionContext.lView !;
expect(sectionContext.nodeIndex).toEqual(HEADER_OFFSET);
expect(sectionLView.length).toBeGreaterThan(HEADER_OFFSET);
expect(sectionContext.native).toBe(section);
const div = fixture.hostElement.querySelector('div') !;
const divContext = getContext(div) !;
const divContext = getLContext(div) !;
const divLView = divContext.lView !;
expect(divContext.nodeIndex).toEqual(HEADER_OFFSET + 1);
expect(divLView.length).toBeGreaterThan(HEADER_OFFSET);
@ -2080,7 +2080,7 @@ describe('render3 integration test', () => {
const result1 = section[MONKEY_PATCH_KEY_NAME];
expect(Array.isArray(result1)).toBeTruthy();
const context = getContext(section) !;
const context = getLContext(section) !;
const result2 = section[MONKEY_PATCH_KEY_NAME];
expect(Array.isArray(result2)).toBeFalsy();
@ -2116,7 +2116,7 @@ describe('render3 integration test', () => {
const p = fixture.hostElement.querySelector('p') !as any;
expect(p[MONKEY_PATCH_KEY_NAME]).toBeFalsy();
const pContext = getContext(p) !;
const pContext = getLContext(p) !;
expect(pContext.native).toBe(p);
expect(p[MONKEY_PATCH_KEY_NAME]).toBe(pContext);
});
@ -2154,7 +2154,7 @@ describe('render3 integration test', () => {
expect(Array.isArray(elementResult)).toBeTruthy();
expect(elementResult[StylingIndex.ElementPosition]).toBe(section);
const context = getContext(section) !;
const context = getLContext(section) !;
const result2 = section[MONKEY_PATCH_KEY_NAME];
expect(Array.isArray(result2)).toBeFalsy();
@ -2248,9 +2248,9 @@ describe('render3 integration test', () => {
expect(pText[MONKEY_PATCH_KEY_NAME]).toBeFalsy();
expect(projectedTextNode[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
const parentContext = getContext(section) !;
const shadowContext = getContext(header) !;
const projectedContext = getContext(p) !;
const parentContext = getLContext(section) !;
const shadowContext = getLContext(header) !;
const projectedContext = getLContext(p) !;
const parentComponentData = parentContext.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',
() => {
const elm1 = document.createElement('div');
const context1 = getContext(elm1);
const context1 = getLContext(elm1);
expect(context1).toBeFalsy();
const elm2 = document.createElement('div');
document.body.appendChild(elm2);
const context2 = getContext(elm2);
const context2 = getLContext(elm2);
expect(context2).toBeFalsy();
});
@ -2296,7 +2296,7 @@ describe('render3 integration test', () => {
const manuallyCreatedElement = document.createElement('div');
section.appendChild(manuallyCreatedElement);
const context = getContext(manuallyCreatedElement);
const context = getLContext(manuallyCreatedElement);
expect(context).toBeFalsy();
});
@ -2324,11 +2324,11 @@ describe('render3 integration test', () => {
const hostLView = (hostElm as any)[MONKEY_PATCH_KEY_NAME];
expect(hostLView).toBe(componentLView);
const context1 = getContext(hostElm) !;
const context1 = getLContext(hostElm) !;
expect(context1.lView).toBe(hostLView);
expect(context1.native).toEqual(hostElm);
const context2 = getContext(component) !;
const context2 = getLContext(component) !;
expect(context2).toBe(context1);
expect(context2.lView).toBe(hostLView);
expect(context2.native).toEqual(hostElm);
@ -2387,7 +2387,7 @@ describe('render3 integration test', () => {
const hostElm = fixture.hostElement;
const div1 = hostElm.querySelector('div:first-child') !as any;
const div2 = hostElm.querySelector('div:last-child') !as any;
const context = getContext(hostElm) !;
const context = getLContext(hostElm) !;
const componentView = context.lView[context.nodeIndex];
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((myDir3Instance as any)[MONKEY_PATCH_KEY_NAME])).toBeTruthy();
const d1Context = getContext(myDir1Instance) !;
const d2Context = getContext(myDir2Instance) !;
const d3Context = getContext(myDir3Instance) !;
const d1Context = getLContext(myDir1Instance) !;
const d2Context = getLContext(myDir2Instance) !;
const d3Context = getLContext(myDir3Instance) !;
expect(d1Context.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((childComponentInstance as any)[MONKEY_PATCH_KEY_NAME]).toBe(lView);
const childNodeContext = getContext(childCompHostElm) !;
const childNodeContext = getLContext(childCompHostElm) !;
expect(childNodeContext.component).toBeFalsy();
expect(childNodeContext.directives).toBeFalsy();
assertMonkeyPatchValueIsLView(myDir1Instance);
assertMonkeyPatchValueIsLView(myDir2Instance);
assertMonkeyPatchValueIsLView(childComponentInstance);
expect(getContext(myDir1Instance)).toBe(childNodeContext);
expect(getLContext(myDir1Instance)).toBe(childNodeContext);
expect(childNodeContext.component).toBeFalsy();
expect(childNodeContext.directives !.length).toEqual(2);
assertMonkeyPatchValueIsLView(myDir1Instance, false);
assertMonkeyPatchValueIsLView(myDir2Instance, false);
assertMonkeyPatchValueIsLView(childComponentInstance);
expect(getContext(myDir2Instance)).toBe(childNodeContext);
expect(getLContext(myDir2Instance)).toBe(childNodeContext);
expect(childNodeContext.component).toBeFalsy();
expect(childNodeContext.directives !.length).toEqual(2);
assertMonkeyPatchValueIsLView(myDir1Instance, false);
assertMonkeyPatchValueIsLView(myDir2Instance, false);
assertMonkeyPatchValueIsLView(childComponentInstance);
expect(getContext(childComponentInstance)).toBe(childNodeContext);
expect(getLContext(childComponentInstance)).toBe(childNodeContext);
expect(childNodeContext.component).toBeTruthy();
expect(childNodeContext.directives !.length).toEqual(2);
assertMonkeyPatchValueIsLView(myDir1Instance, false);
@ -2565,7 +2565,7 @@ describe('render3 integration test', () => {
const child = host.querySelector('child-comp') as any;
expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
const context = getContext(child) !;
const context = getLContext(child) !;
expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
const componentData = context.lView[context.nodeIndex];
@ -2573,7 +2573,7 @@ describe('render3 integration test', () => {
expect(component instanceof ChildComp).toBeTruthy();
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(componentContext.nodeIndex).toEqual(context.nodeIndex);
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_RENDERER2_FACTORY__POST_R3__ as R3_RENDERER2_FACTORY} from '../../src/render/api';
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 {NG_ELEMENT_ID} from '../../src/render3/fields';
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 {
let element: any;
if (isComponentInstance(componentOrElement)) {
const context = getContext(componentOrElement);
const context = getLContext(componentOrElement);
element = context ? context.native : null;
} else {
element = componentOrElement;

View File

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

View File

@ -1,11 +1,15 @@
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 getHostElement<T>(directive: T): Element;
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 getRootComponents(target: {}): any[];