diff --git a/projects/ng-devtools-backend/src/lib/change-detection-tracker.ts b/projects/ng-devtools-backend/src/lib/change-detection-tracker.ts index 3807b878153..7bc44120b8f 100644 --- a/projects/ng-devtools-backend/src/lib/change-detection-tracker.ts +++ b/projects/ng-devtools-backend/src/lib/change-detection-tracker.ts @@ -7,7 +7,7 @@ export const onChangeDetection = (callback: () => void): void => { if (hookInitialized) { return; } - const forest = getDirectiveForest(); + const forest = getDirectiveForest(document.documentElement, (window as any).ng); listenAndNotifyOnUpdates(forest, callback); hookInitialized = true; }; diff --git a/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts b/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts index 21cf6b2b7fe..a0b3c848b8a 100644 --- a/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts +++ b/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts @@ -59,7 +59,7 @@ const initChangeDetection = (messageBus: MessageBus) => { const getLatestComponentExplorerViewCallback = (messageBus: MessageBus) => query => { messageBus.emit('latestComponentExplorerView', [ { - forest: prepareForestForSerialization(getDirectiveForest()), + forest: prepareForestForSerialization(getDirectiveForest(document.documentElement, (window as any).ng)), properties: getLatestComponentState(query), }, ]); @@ -74,7 +74,7 @@ const stopProfilingCallback = (messageBus: MessageBus) => () => { }; const getElementDirectivesPropertiesCallback = (messageBus: MessageBus) => (id: ElementID) => { - const node = queryComponentForest(id, getDirectiveForest()); + const node = queryComponentForest(id, getDirectiveForest(document.documentElement, (window as any).ng)); if (node) { messageBus.emit('elementDirectivesProperties', [serializeNodeDirectiveProperties(node)]); } else { @@ -83,12 +83,12 @@ const getElementDirectivesPropertiesCallback = (messageBus: MessageBus) }; const selectedComponentCallback = (id: ElementID) => { - const node = queryComponentForest(id, getDirectiveForest()); + const node = queryComponentForest(id, getDirectiveForest(document.documentElement, (window as any).ng)); setConsoleReference(node); }; const getNestedPropertiesCallback = (messageBus: MessageBus) => (id: DirectiveID, propPath: string[]) => { - const node = queryComponentForest(id.element, getDirectiveForest()); + const node = queryComponentForest(id.element, getDirectiveForest(document.documentElement, (window as any).ng)); if (node) { let current = (id.directive === undefined ? node.component : node.directives[id.directive]).instance; for (const prop of propPath) { diff --git a/projects/ng-devtools-backend/src/lib/component-inspector/component-inspector.ts b/projects/ng-devtools-backend/src/lib/component-inspector/component-inspector.ts index db48b8a5f24..2c6db6d312f 100644 --- a/projects/ng-devtools-backend/src/lib/component-inspector/component-inspector.ts +++ b/projects/ng-devtools-backend/src/lib/component-inspector/component-inspector.ts @@ -61,7 +61,7 @@ export class ComponentInspector { unHighlight(); if (this._selectedComponent.component) { highlight(this._selectedComponent.host); - const forest: IndexedNode[] = indexForest(getDirectiveForest()); + const forest: IndexedNode[] = indexForest(getDirectiveForest(document.documentElement, (window as any).ng)); const elementId: ElementID = getIndexForNativeElementInForest(this._selectedComponent.host, forest); this._onComponentEnter(elementId); } @@ -81,7 +81,7 @@ export class ComponentInspector { } highlightById(id: ElementID): void { - const forest: ComponentTreeNode[] = getDirectiveForest(); + const forest: ComponentTreeNode[] = getDirectiveForest(document.documentElement, (window as any).ng); const elementToHighlight: HTMLElement = findNodeInForest(id, forest); highlight(elementToHighlight); } diff --git a/projects/ng-devtools-backend/src/lib/component-tree.ts b/projects/ng-devtools-backend/src/lib/component-tree.ts index 3df05de13db..79f7a59f534 100644 --- a/projects/ng-devtools-backend/src/lib/component-tree.ts +++ b/projects/ng-devtools-backend/src/lib/component-tree.ts @@ -1,7 +1,5 @@ import { deeplySerializeSelectedProperties } from './state-serializer/state-serializer'; -declare const ng: any; - import { ComponentType, DirectiveType, @@ -12,6 +10,7 @@ import { } from 'protocol'; import { getComponentName } from './highlighter'; import { IndexedNode } from './recording/observer'; +import { DebuggingAPI } from './interfaces'; export interface DirectiveInstanceType extends DirectiveType { instance: any; @@ -32,7 +31,10 @@ export interface DirectiveForestBuilderOptions { export const getLatestComponentState = (query: ComponentExplorerViewQuery): DirectivesProperties | undefined => { let result: DirectivesProperties | undefined; if (query.selectedElement && query.expandedProperties) { - const node = queryComponentForest(query.selectedElement, getDirectiveForest()); + const node = queryComponentForest( + query.selectedElement, + getDirectiveForest(document.documentElement, (window as any).ng) + ); if (!node) { return undefined; } @@ -80,20 +82,22 @@ export const prepareForestForSerialization = (roots: ComponentTreeNode[]): Compo }); }; -export const getDirectiveForest = (root = document.documentElement): ComponentTreeNode[] => +export const getDirectiveForest = (root: HTMLElement, ngd: DebuggingAPI): ComponentTreeNode[] => buildDirectiveForest( root, { element: '__ROOT__', component: null, directives: [], children: [] }, - { getDirectives: true } + { getDirectives: true }, + ngd ); -export const getComponentForest = (root = document.documentElement): ComponentTreeNode[] => - buildDirectiveForest(root, { element: '__ROOT__', component: null, directives: [], children: [] }); +export const getComponentForest = (root: HTMLElement, ngd: DebuggingAPI): ComponentTreeNode[] => + buildDirectiveForest(root, { element: '__ROOT__', component: null, directives: [], children: [] }, {}, ngd); const buildDirectiveForest = ( node: Element, tree: ComponentTreeNode | undefined, - options: DirectiveForestBuilderOptions = {} + options: DirectiveForestBuilderOptions = {}, + ngd: DebuggingAPI ): ComponentTreeNode[] => { if (!node) { return [tree]; @@ -101,17 +105,17 @@ const buildDirectiveForest = ( let dirs = []; if (tree.element !== '__ROOT__' && options.getDirectives) { // Need to make sure we're in a component tree - // otherwise, ng.getDirectives will throw without + // otherwise, ngd.getDirectives will throw without // a root node. try { - dirs = ng.getDirectives(node) || []; + dirs = ngd.getDirectives(node) || []; } catch (e) { console.warn('Cannot find context for element', node); } } - const cmp = ng.getComponent(node); + const cmp = ngd.getComponent(node); if (!cmp && !dirs.length) { - Array.from(node.children).forEach(c => buildDirectiveForest(c, tree, options)); + Array.from(node.children).forEach(c => buildDirectiveForest(c, tree, options, ngd)); return tree.children; } const current: ComponentTreeNode = { @@ -137,7 +141,7 @@ const buildDirectiveForest = ( current.element = node.tagName.toLowerCase(); } tree.children.push(current); - Array.from(node.children).forEach(c => buildDirectiveForest(c, current, options)); + Array.from(node.children).forEach(c => buildDirectiveForest(c, current, options, ngd)); return tree.children; }; @@ -175,14 +179,15 @@ const findElementIDFromNativeElementInForest = ( forest: IndexedNode[], nativeElement: HTMLElement ): ElementID | null => { - for (let i = 0; i < forest.length; i++) { - if (forest[i].nativeElement === nativeElement) { - return forest[i].id; + for (const el of forest) { + if (el.nativeElement === nativeElement) { + return el.id; } } - for (let i = 0; i < forest.length; i++) { - if (forest[i].children.length) { - return findElementIDFromNativeElementInForest(forest[i].children, nativeElement); + + for (const el of forest) { + if (el.children.length) { + return findElementIDFromNativeElementInForest(el.children, nativeElement); } } return null; @@ -190,5 +195,5 @@ const findElementIDFromNativeElementInForest = ( export const findNodeFromSerializedPathId = (serializedId: string) => { const id: number[] = serializedId.split(',').map(index => parseInt(index, 10)); - return queryComponentForest(id, getDirectiveForest()); + return queryComponentForest(id, getDirectiveForest(document.documentElement, (window as any).ng)); }; diff --git a/projects/ng-devtools-backend/src/lib/interfaces.ts b/projects/ng-devtools-backend/src/lib/interfaces.ts new file mode 100644 index 00000000000..ade3947cb88 --- /dev/null +++ b/projects/ng-devtools-backend/src/lib/interfaces.ts @@ -0,0 +1,5 @@ +export interface DebuggingAPI { + getComponent(node: Node): any; + getDirectives(node: Node): any[]; + getHostElement(cmp: any): HTMLElement; +} diff --git a/projects/ng-devtools-backend/src/lib/recording/identity-tracker.spec.ts b/projects/ng-devtools-backend/src/lib/recording/identity-tracker.spec.ts new file mode 100644 index 00000000000..61749590b3d --- /dev/null +++ b/projects/ng-devtools-backend/src/lib/recording/identity-tracker.spec.ts @@ -0,0 +1,139 @@ +import { IdentityTracker } from './identity-tracker'; +import { DebuggingAPI } from '../interfaces'; +import { DirectiveInstanceType } from '../component-tree'; +import { debug } from 'ng-packagr/lib/utils/log'; + +let debuggingAPI: DebuggingAPI = { + getComponent(node: Node): any {}, + getDirectives(node: Node): any[] { + return []; + }, + getHostElement(cmp: any): HTMLElement { + return null; + }, +}; + +describe('identity tracker', () => { + let tracker: IdentityTracker; + + beforeEach(() => { + tracker = new IdentityTracker(debuggingAPI); + }); + + it('should index trees', () => { + const dirInstance = {}; + const dir: DirectiveInstanceType = { + instance: dirInstance, + name: 'DIR', + }; + const cmpInstance = {}; + const nested = { + id: [0, 0], + element: 'CMP2', + component: { + name: 'CMP2', + instance: cmpInstance, + }, + directives: [dir], + nativeElement: undefined, + children: [], + }; + tracker.index({ + children: [nested], + nativeElement: undefined, + directives: [], + component: { + instance: {}, + name: 'CMP1', + }, + element: 'CMP', + id: [0], + }); + + expect(tracker.getDirectiveID(dirInstance)).toEqual([0, 0]); + expect(tracker.getDirectiveID(cmpInstance)).toEqual([0, 0]); + }); + + it('should update indexes on insertion', () => { + const childEl = { + children: [], + parentElement: null, + tagName: 'child', + }; + const childCmp = { + name: 'childCmp', + }; + + const siblingEl = { + children: [], + parentElement: null, + tagName: 'sibling', + }; + const siblingCmp = { + name: 'siblingCmp', + }; + + const rootEl = { + children: [childEl, siblingEl], + tagName: 'parent', + }; + const rootCmp = { + name: 'rootCmp', + }; + + childEl.parentElement = rootEl; + siblingEl.parentElement = rootEl; + + const nested = { + id: [0, 0], + element: 'CMP2', + component: { + name: 'CMP2', + instance: childCmp, + }, + directives: [], + nativeElement: childEl as any, + children: [], + }; + + const nodeComponent = new Map(); + nodeComponent.set(rootEl, rootCmp); + nodeComponent.set(childEl, childCmp); + nodeComponent.set(siblingEl, siblingCmp); + + const componentNode = new Map(); + componentNode.set(rootCmp, rootEl); + componentNode.set(childCmp, childEl); + componentNode.set(siblingCmp, siblingEl); + + debuggingAPI = { + getComponent(node: Node): any { + return nodeComponent.get(node); + }, + getDirectives(node: Node): any[] { + return []; + }, + getHostElement(cmp: any): HTMLElement { + return componentNode.get(cmp); + }, + }; + + tracker = new IdentityTracker(debuggingAPI); + + tracker.index({ + children: [nested], + nativeElement: rootEl as any, + directives: [], + component: { + instance: rootCmp, + name: 'CMP1', + }, + element: 'CMP', + id: [0], + }); + + expect(tracker.getDirectiveID(rootCmp)).toEqual([0]); + tracker.insert(siblingEl as any, siblingCmp); + expect(tracker.getDirectiveID(siblingCmp)).toEqual([0, 1]); + }); +}); diff --git a/projects/ng-devtools-backend/src/lib/recording/identity-tracker.ts b/projects/ng-devtools-backend/src/lib/recording/identity-tracker.ts new file mode 100644 index 00000000000..fbac563b293 --- /dev/null +++ b/projects/ng-devtools-backend/src/lib/recording/identity-tracker.ts @@ -0,0 +1,176 @@ +import { ElementID } from 'protocol'; +import { getComponentForest } from '../component-tree'; +import { Type } from '@angular/core'; +import { IndexedNode } from './observer'; +import { DebuggingAPI } from '../interfaces'; + +interface TreeNode { + parent: TreeNode; + component?: Type; + directives?: Type[]; + children: TreeNode[]; +} + +export class IdentityTracker { + private _elementComponent = new Map(); + private _elementDirectives = new Map(); + private _currentDirectiveID = new Map(); + private _createdDirectives = new Set(); + private _forest: TreeNode[] = []; + private _componentTreeNode = new Map(); + + constructor(private _ng: DebuggingAPI) {} + + getDirectiveID(dir: any) { + return this._currentDirectiveID.get(dir); + } + + insert(node: HTMLElement, cmpOrDirective: any | any[]): void { + const isComponent = !Array.isArray(cmpOrDirective); + const parent = getParentComponentFromDomNode(node, this._ng); + + (isComponent ? [cmpOrDirective] : cmpOrDirective).forEach((dir: any) => { + this._elementComponent.set(node, dir); + this._createdDirectives.add(dir); + }); + + let parentTreeNode = null; + let parentID = []; + let childIdx = 0; + let siblingsArray = this._forest; + if (parent) { + const parentElement = this._ng.getHostElement(parent) || document.documentElement; + + const children = getComponentForest(parentElement, this._ng)[0].children; + + parentID = this._currentDirectiveID.get(parent); + parentTreeNode = this._componentTreeNode.get(parent); + siblingsArray = parentTreeNode.children; + + if (isComponent) { + for (const child of children) { + if (child.component.instance === cmpOrDirective) { + break; + } + if (this._createdDirectives.has(child.component.instance)) { + childIdx++; + } + } + } else { + for (const child of children) { + if (child.directives && child.directives.length && child.directives.some(d => d === cmpOrDirective[0])) { + break; + } + if (this._createdDirectives.has(child.component.instance)) { + childIdx++; + } + } + } + } + + const treeNode: TreeNode = { + parent: parentTreeNode, + children: [], + directives: isComponent ? undefined : cmpOrDirective, + component: isComponent ? cmpOrDirective : cmpOrDirective[0], + }; + siblingsArray.splice(childIdx, 0, treeNode); + + if (isComponent) { + this._currentDirectiveID.set(cmpOrDirective, parentID.concat([childIdx])); + this._componentTreeNode.set(cmpOrDirective, treeNode); + } else { + const elID = parentID.concat([childIdx]); + cmpOrDirective.forEach((dir: any) => { + this._currentDirectiveID.set(dir, elID); + this._componentTreeNode.set(dir, treeNode); + }); + } + + for (let i = childIdx + 1; i < siblingsArray.length; i++) { + const sibling = siblingsArray[i]; + const siblingId = this._currentDirectiveID.get(sibling.component); + siblingId[siblingId.length - 1] = siblingId[siblingId.length - 1] + 1; + this._updateNestedNodeIds(sibling, siblingId.length - 1, 1); + } + } + + delete(cmp: any): void { + const node = this._componentTreeNode.get(cmp); + const parent = node.parent; + let childrenArray = this._forest; + if (parent) { + childrenArray = parent.children; + } + + const childIdx = childrenArray.indexOf(node); + childrenArray.splice(childIdx, 1); + + for (let i = childIdx; i < childrenArray.length; i++) { + const sibling = childrenArray[i].component; + const siblingId = this._currentDirectiveID.get(sibling); + // We removed the sibling node, so we need to decrease the position + siblingId[siblingId.length - 1] = siblingId[siblingId.length - 1] - 1; + this._updateNestedNodeIds(childrenArray[i], siblingId.length - 1, -1); + } + } + + index(root: IndexedNode, parent: TreeNode | null = null): void { + if (root.component) { + this._createdDirectives.add(root.component.instance); + const node = { + component: root.component.instance, + parent, + directives: [], + children: [], + }; + this._componentTreeNode.set(root.component.instance, node); + if (parent) { + parent.children.push(node); + } else { + this._forest.push(node); + } + parent = node; + this._currentDirectiveID.set(root.component.instance, root.id); + root.directives.forEach(dir => { + this._currentDirectiveID.set(dir.instance, root.id); + }); + } + root.children.forEach(child => this.index(child, parent)); + } + + hasDirective(dir: any) { + return this._createdDirectives.has(dir); + } + + destroy() { + this._elementComponent = new Map(); + this._currentDirectiveID = new Map(); + this._createdDirectives = new Set(); + this._elementDirectives = new Map(); + this._forest = []; + this._componentTreeNode = new Map(); + } + + private _updateNestedNodeIds(p: TreeNode, level: number, incrementBy: number): void { + p.children.forEach(c => { + const id = this._currentDirectiveID.get(c.component); + id[level] = id[level] + incrementBy; + this._updateNestedNodeIds(c, level, incrementBy); + }); + } +} + +const getParentComponentFromDomNode = (node: Node, ng: DebuggingAPI) => { + let current = node; + let parent = null; + while (current.parentElement) { + current = current.parentElement; + const parentComponent = ng.getComponent(current); + if (parentComponent) { + parent = parentComponent; + break; + } + } + return parent; +}; diff --git a/projects/ng-devtools-backend/src/lib/recording/observer.ts b/projects/ng-devtools-backend/src/lib/recording/observer.ts index 843c83b8d86..1656cd27812 100644 --- a/projects/ng-devtools-backend/src/lib/recording/observer.ts +++ b/projects/ng-devtools-backend/src/lib/recording/observer.ts @@ -1,23 +1,30 @@ import { ElementID, Node as ComponentNode } from 'protocol'; import { ComponentInstanceType, ComponentTreeNode, DirectiveInstanceType, getComponentForest } from '../component-tree'; import { componentMetadata } from '../utils'; +import { IdentityTracker } from './identity-tracker'; + +export type LifecyleHook = + | 'ngOnInit' + | 'ngOnDestroy' + | 'ngOnChanges' + | 'ngDoCheck' + | 'ngAfterContentInit' + | 'ngAfterContentChecked' + | 'ngAfterViewInit' + | 'ngAfterViewChecked'; export type CreationCallback = (component: any, id: ElementID) => void; +export type LifecycleCallback = (component: any, hook: LifecyleHook, duration: any) => void; export type ChangeDetectionCallback = (component: any, id: ElementID, duration: number) => void; export type DestroyCallback = (component: any, id: ElementID) => void; -interface TreeNode { - parent: TreeNode; - component: any; - children: TreeNode[]; -} - declare const ng: any; export interface Config { onCreate: CreationCallback; onDestroy: DestroyCallback; onChangeDetection: ChangeDetectionCallback; + onLifecycleHook: LifecycleCallback; } /** @@ -30,13 +37,9 @@ export interface Config { */ export class ComponentTreeObserver { private _mutationObserver = new MutationObserver(this._onMutation.bind(this)); - private _elementComponent = new Map(); private _patched = new Map void>(); - private _currentComponentID = new Map(); private _lastChangeDetection = new Map(); - private _createdComponents = new Set(); - private _forest: TreeNode[] = []; - private _componentTreeNode = new Map(); + private _tracker = new IdentityTracker((window as any).ng); constructor(private _config: Partial) {} @@ -54,12 +57,8 @@ export class ComponentTreeObserver { destroy() { this._mutationObserver.disconnect(); - this._elementComponent = new Map(); - this._currentComponentID = new Map(); this._lastChangeDetection = new Map(); - this._createdComponents = new Set(); - this._forest = []; - this._componentTreeNode = new Map(); + this._tracker.destroy(); for (const [cmp, template] of this._patched) { const meta = componentMetadata(cmp); @@ -81,16 +80,11 @@ export class ComponentTreeObserver { } const component = ng.getComponent(node); if (component) { - this._elementComponent.set(node, component); - if (this._config.onChangeDetection) { this._observeComponent(component); } - const parentComponent = getParentComponentFromDomNode(node); - this._updateInsertionID(component, parentComponent); - - this._createdComponents.add(component); + this._tracker.insert(node, component); this._fireCreationCallback(component); @@ -99,110 +93,67 @@ export class ComponentTreeObserver { this._lastChangeDetection.delete(component); } } + + let directives = []; + try { + directives = ng.getDirectives(node); + } catch {} + if (directives.length) { + this._tracker.insert(node, directives); + directives.forEach(dir => { + this._fireCreationCallback(dir); + }); + } } - private _fireCreationCallback(component): void { - const id = this._currentComponentID.get(component); + private _fireCreationCallback(component: any): void { + const id = this._tracker.getDirectiveID(component); this._config.onCreate(component, id); } private _fireChangeDetectionCallback(component): void { this._config.onChangeDetection( component, - this._currentComponentID.get(component), + this._tracker.getDirectiveID(component), this._lastChangeDetection.get(component) ); } private _onDeletedNodesMutation(node: Node): void { - const component = this._elementComponent.get(node); + if (!(node instanceof HTMLElement)) { + return; + } + + const component = ng.getComponent(node); if (component) { - this._elementComponent.delete(node); - this._updateDeletionID(component); + this._tracker.delete(component); this._fireDestroyCallback(component); } + + let directives = []; + try { + directives = ng.getDirectives(node); + } catch {} + + if (directives && directives.length) { + this._tracker.delete(directives[0]); + directives.forEach(dir => { + this._fireDestroyCallback(dir); + }); + } } - private _fireDestroyCallback(component): void { - const id = this._currentComponentID.get(component); + private _fireDestroyCallback(component: any): void { + const id = this._tracker.getDirectiveID(component); this._config.onDestroy(component, id); } - private _updateInsertionID(cmp: any, parent: any): void { - let parentTreeNode = null; - let parentID = []; - let childIdx = 0; - let siblingsArray = this._forest; - if (parent) { - const parentElement = ng.getHostElement(parent || document.documentElement); - const children = getComponentForest(parentElement)[0].children; - - // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < children.length; i++) { - if (children[i].component.instance === cmp) { - break; - } - if (this._componentTreeNode.has(children[i].component.instance)) { - childIdx++; - } - } - parentID = this._currentComponentID.get(parent); - parentTreeNode = this._componentTreeNode.get(parent); - siblingsArray = parentTreeNode.children; - } - - const treeNode: TreeNode = { - parent: parentTreeNode, - children: [], - component: cmp, - }; - siblingsArray.splice(childIdx, 0, treeNode); - this._currentComponentID.set(cmp, parentID.concat([childIdx])); - this._componentTreeNode.set(cmp, treeNode); - - for (let i = childIdx + 1; i < siblingsArray.length; i++) { - const sibling = siblingsArray[i]; - const siblingId = this._currentComponentID.get(sibling.component); - siblingId[siblingId.length - 1] = siblingId[siblingId.length - 1] + 1; - this._updateNestedNodeIds(sibling, siblingId.length - 1, 1); - } - } - - private _updateDeletionID(cmp: any): void { - const node = this._componentTreeNode.get(cmp); - const parent = node.parent; - let childrenArray = this._forest; - if (parent) { - childrenArray = parent.children; - } - - const childIdx = childrenArray.indexOf(node); - childrenArray.splice(childIdx, 1); - - for (let i = childIdx; i < childrenArray.length; i++) { - const sibling = childrenArray[i].component; - const siblingId = this._currentComponentID.get(sibling); - // We removed the sibling node, so we need to decrease the position - siblingId[siblingId.length - 1] = siblingId[siblingId.length - 1] - 1; - this._updateNestedNodeIds(childrenArray[i], siblingId.length - 1, -1); - } - } - - private _updateNestedNodeIds(p: TreeNode, level: number, incrementBy: number): void { - p.children.forEach(c => { - const id = this._currentComponentID.get(c.component); - id[level] = id[level] + incrementBy; - this._updateNestedNodeIds(c, level, incrementBy); - }); - } - private _initializeChangeDetectionObserver(root: Element = document.documentElement): void { if (!(root instanceof HTMLElement)) { return; } const cmp = ng.getComponent(root); if (cmp) { - this._elementComponent.set(root, cmp); this._observeComponent(cmp); } // tslint:disable:prefer-for-of @@ -221,8 +172,8 @@ export class ComponentTreeObserver { declarations.tView.template = function(_, component: any) { const start = performance.now(); original.apply(this, arguments); - if (self._createdComponents.has(component)) { - self._config.onChangeDetection(component, self._currentComponentID.get(component), performance.now() - start); + if (self._tracker.hasDirective(component)) { + self._config.onChangeDetection(component, self._tracker.getDirectiveID(component), performance.now() - start); } else { self._lastChangeDetection.set(component, performance.now() - start); } @@ -232,32 +183,14 @@ export class ComponentTreeObserver { } private _indexTree(): void { - const componentForest = indexForest(getComponentForest(document.documentElement)); - componentForest.forEach(root => this._setIndexes(root)); - } - - private _setIndexes(root: IndexedNode, parent: TreeNode | null = null): void { - if (root.component) { - this._createdComponents.add(root.component.instance); - const node = { - component: root.component.instance, - parent, - children: [], - }; - this._componentTreeNode.set(root.component.instance, node); - if (parent) { - parent.children.push(node); - } else { - this._forest.push(node); - } - parent = node; - this._currentComponentID.set(root.component.instance, root.id); - } - root.children.forEach(child => this._setIndexes(child, parent)); + const componentForest = indexForest(getComponentForest(document.documentElement, (window as any).ng)); + componentForest.forEach(root => this._tracker.index(root)); } private _createOriginalTree(): void { - getComponentForest().forEach(root => this._fireInitialTreeCallbacks(root)); + getComponentForest(document.documentElement, (window as any).ng).forEach(root => + this._fireInitialTreeCallbacks(root) + ); } private _fireInitialTreeCallbacks(root: ComponentTreeNode) { @@ -288,18 +221,4 @@ const indexTree = (node: ComponentNode, idx: number, parentId = []): IndexedNode } as IndexedNode; }; -const getParentComponentFromDomNode = (node: Node) => { - let current = node; - let parent = null; - while (current.parentElement) { - current = current.parentElement; - const parentComponent = ng.getComponent(current); - if (parentComponent) { - parent = parentComponent; - break; - } - } - return parent; -}; - export const indexForest = (forest: ComponentNode[]): IndexedNode[] => forest.map((n, i) => indexTree(n, i));