mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(devtools): move identity tracking to separate abstraction
This commit is contained in:
parent
4d8ca6565a
commit
03f896c48e
8 changed files with 409 additions and 165 deletions
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ const initChangeDetection = (messageBus: MessageBus<Events>) => {
|
|||
const getLatestComponentExplorerViewCallback = (messageBus: MessageBus<Events>) => 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<Events>) => () => {
|
|||
};
|
||||
|
||||
const getElementDirectivesPropertiesCallback = (messageBus: MessageBus<Events>) => (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<Events>)
|
|||
};
|
||||
|
||||
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<Events>) => (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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
};
|
||||
|
|
|
|||
5
projects/ng-devtools-backend/src/lib/interfaces.ts
Normal file
5
projects/ng-devtools-backend/src/lib/interfaces.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export interface DebuggingAPI {
|
||||
getComponent(node: Node): any;
|
||||
getDirectives(node: Node): any[];
|
||||
getHostElement(cmp: any): HTMLElement;
|
||||
}
|
||||
|
|
@ -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<any, any>();
|
||||
nodeComponent.set(rootEl, rootCmp);
|
||||
nodeComponent.set(childEl, childCmp);
|
||||
nodeComponent.set(siblingEl, siblingCmp);
|
||||
|
||||
const componentNode = new Map<any, any>();
|
||||
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]);
|
||||
});
|
||||
});
|
||||
|
|
@ -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<any>;
|
||||
directives?: Type<any>[];
|
||||
children: TreeNode[];
|
||||
}
|
||||
|
||||
export class IdentityTracker {
|
||||
private _elementComponent = new Map<Node, any>();
|
||||
private _elementDirectives = new Map<Node, any[]>();
|
||||
private _currentDirectiveID = new Map<any, ElementID>();
|
||||
private _createdDirectives = new Set<any>();
|
||||
private _forest: TreeNode[] = [];
|
||||
private _componentTreeNode = new Map<any, TreeNode>();
|
||||
|
||||
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<Node, any>();
|
||||
this._currentDirectiveID = new Map<any, ElementID>();
|
||||
this._createdDirectives = new Set<any>();
|
||||
this._elementDirectives = new Map<Node, any[]>();
|
||||
this._forest = [];
|
||||
this._componentTreeNode = new Map<any, TreeNode>();
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
@ -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<Node, any>();
|
||||
private _patched = new Map<any, () => void>();
|
||||
private _currentComponentID = new Map<any, ElementID>();
|
||||
private _lastChangeDetection = new Map<any, number>();
|
||||
private _createdComponents = new Set<any>();
|
||||
private _forest: TreeNode[] = [];
|
||||
private _componentTreeNode = new Map<any, TreeNode>();
|
||||
private _tracker = new IdentityTracker((window as any).ng);
|
||||
|
||||
constructor(private _config: Partial<Config>) {}
|
||||
|
||||
|
|
@ -54,12 +57,8 @@ export class ComponentTreeObserver {
|
|||
|
||||
destroy() {
|
||||
this._mutationObserver.disconnect();
|
||||
this._elementComponent = new Map<Node, any>();
|
||||
this._currentComponentID = new Map<any, ElementID>();
|
||||
this._lastChangeDetection = new Map<any, number>();
|
||||
this._createdComponents = new Set<any>();
|
||||
this._forest = [];
|
||||
this._componentTreeNode = new Map<any, TreeNode>();
|
||||
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));
|
||||
|
|
|
|||
Loading…
Reference in a new issue