Skip to content

Commit

Permalink
feat(devtools): create message bus event for component selection
Browse files Browse the repository at this point in the history
  • Loading branch information
AleksanderBodurri committed Jan 31, 2020
1 parent 05405fa commit 26fb1f4
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 12 deletions.
12 changes: 11 additions & 1 deletion projects/ng-devtools-backend/src/lib/client-event-subscribers.ts
Expand Up @@ -2,14 +2,15 @@ import { DirectiveID, DirectivesProperties, ElementID, Events, MessageBus } from
import { onChangeDetection } from './change-detection-tracker';
import {
ComponentTreeNode,
getDirectiveForest,
getDirectiveForest, getForestWithNativeElements,
getLatestComponentState,
queryComponentTree,
trimComponents,
} from './component-tree';
import { start as startProfiling, stop as stopProfiling } from './recording';
import { serializeComponentState } from './state-serializer';
import { ComponentInspector } from './component-inspector';
import { setConsoleReference } from './selected-component';

const inspector: ComponentInspector = new ComponentInspector();

Expand Down Expand Up @@ -47,6 +48,15 @@ export const subscribeToClientEvents = (messageBus: MessageBus<Events>): void =>
}
});

messageBus.on('setSelectedComponent', (id: ElementID) => {
const node = queryComponentTree(id, getForestWithNativeElements);
// If message bus implements a getWindow function, use that window as the reference for setConsoleReference().
// This is done so that if the dev tools are communicating with an iframe, the variable $ng0
// is set at the parent window of that iframe instead of the iframe window.
const bus = (messageBus as any);
setConsoleReference(typeof bus.getWindow === 'function' ? bus.getWindow() : window, node);
});

messageBus.on('getNestedProperties', (id: DirectiveID, propPath: string[]) => {
const node = queryComponentTree(id.element);
if (node) {
Expand Down
24 changes: 16 additions & 8 deletions projects/ng-devtools-backend/src/lib/component-tree.ts
Expand Up @@ -69,26 +69,30 @@ export const trimComponents = (roots: ComponentTreeNode[]): ComponentTreeNode[]
: null,
directives: node.directives.map(d => ({ name: d.name })),
children: trimComponents(node.children),
nativeElement: node.nativeElement
} as ComponentTreeNode;
});
};

export const getForestWithNativeElements = (root = document.documentElement): ComponentTreeNode[] =>
buildDirectiveForest(root, { element: '__ROOT__', component: null, directives: [], children: [] }, { getDirectives: true, includeNativeElement: true});

export const getDirectiveForest = (root = document.documentElement): ComponentTreeNode[] =>
buildDirectiveForest(root, { element: '__ROOT__', component: null, directives: [], children: [] }, true);
buildDirectiveForest(root, { element: '__ROOT__', component: null, directives: [], children: [] }, { getDirectives: true});

export const getComponentForest = (root = document.documentElement): ComponentTreeNode[] =>
buildDirectiveForest(root, { element: '__ROOT__', component: null, directives: [], children: [] }, false);
buildDirectiveForest(root, { element: '__ROOT__', component: null, directives: [], children: [] });

const buildDirectiveForest = (
node: Element,
tree: ComponentTreeNode | undefined,
getDirectives: boolean
options: { [option: string]: boolean } = {}
): ComponentTreeNode[] => {
if (!node) {
return [tree];
}
let dirs = [];
if (tree.element !== '__ROOT__' && getDirectives) {
if (tree.element !== '__ROOT__' && options.getDirectives) {
// Need to make sure we're in a component tree
// otherwise, ng.getDirectives will throw without
// a root node.
Expand All @@ -100,7 +104,7 @@ const buildDirectiveForest = (
}
const cmp = ng.getComponent(node);
if (!cmp && !dirs.length) {
Array.from(node.children).forEach(c => buildDirectiveForest(c, tree, getDirectives));
Array.from(node.children).forEach(c => buildDirectiveForest(c, tree, options));
return tree.children;
}
const current: ComponentTreeNode = {
Expand All @@ -115,6 +119,10 @@ const buildDirectiveForest = (
children: [],
};

if (options.includeNativeElement) {
current.nativeElement = () => node;
}

if (cmp) {
current.component = {
instance: cmp,
Expand All @@ -125,17 +133,17 @@ const buildDirectiveForest = (
current.element = node.tagName.toLowerCase();
}
tree.children.push(current);
Array.from(node.children).forEach(c => buildDirectiveForest(c, current, getDirectives));
Array.from(node.children).forEach(c => buildDirectiveForest(c, current, options));
return tree.children;
};

// Based on an ElementID we return a specific component node.
// If we can't find any, we return null.
export const queryComponentTree = (id: ElementID): ComponentTreeNode | null => {
export const queryComponentTree = (id: ElementID, treeGenerator: (root?: HTMLElement) => ComponentTreeNode[] = getDirectiveForest): ComponentTreeNode | null => {
if (!id.length) {
return null;
}
let forest = getDirectiveForest();
let forest = treeGenerator();
let node: null | ComponentTreeNode = null;
for (const i of id) {
node = forest[i];
Expand Down
17 changes: 17 additions & 0 deletions projects/ng-devtools-backend/src/lib/selected-component.ts
@@ -0,0 +1,17 @@
import { ComponentTreeNode } from './component-tree';

declare const ng: any;

const SELECTED_COMPONENT_PROPERTY_KEY_BASE = '$ng0';

export const setConsoleReference = (windowRef: Window, node: ComponentTreeNode) => {
Object.defineProperty(windowRef, SELECTED_COMPONENT_PROPERTY_KEY_BASE, {
get: () => {
if (node) {
return ng.getComponent(node.nativeElement()) || ng.getDebugNode(node.nativeElement());
}
return node.nativeElement();
},
configurable: true
});
};
4 changes: 2 additions & 2 deletions projects/ng-devtools-backend/src/lib/utils.ts
Expand Up @@ -12,8 +12,8 @@ export const patchTemplate = (instance: any, fn: () => void) => {
const metadata = componentMetadata(instance);
const original = metadata.template;

metadata.tView.template = metadata.template = function () {
const result = original.apply(this, arguments);
metadata.tView.template = metadata.template = (...args) => {
const result = original(...args);
fn();
return result;
};
Expand Down
Expand Up @@ -31,6 +31,7 @@ export class ComponentExplorerComponent implements OnInit {
handleNodeSelection(node: IndexedNode): void {
this.currentSelectedElement = node;
this.messageBus.emit('getElementDirectivesProperties', [node.id]);
this.messageBus.emit('setSelectedComponent', [node.id]);
}

ngOnInit(): void {
Expand Down
3 changes: 3 additions & 0 deletions projects/protocol/src/lib/messages.ts
Expand Up @@ -11,6 +11,7 @@ export interface Node<DirType = DirectiveType, CmpType = ComponentType> {
directives: DirType[];
component: CmpType | null;
children: Node[];
nativeElement?: () => Element;
}

export enum PropType {
Expand Down Expand Up @@ -122,6 +123,8 @@ export interface Events {
getNestedProperties: (id: DirectiveID, path: string[]) => void;
nestedProperties: (id: DirectiveID, data: Properties, path: string[]) => void;

setSelectedComponent: (id: ElementID) => void;

componentTreeDirty: () => void;
getLatestComponentExplorerView: (query: ComponentExplorerViewQuery) => void;
latestComponentExplorerView: (view: ComponentExplorerView) => void;
Expand Down
6 changes: 5 additions & 1 deletion src/iframe-message-bus.ts
Expand Up @@ -66,8 +66,12 @@ export class IFrameMessageBus extends MessageBus<Events> {
);
}

destroy() {
destroy(): void {
this._listeners.forEach(l => window.removeEventListener('message', l));
this._listeners = [];
}

getWindow(): Window {
return this._docWindow;
}
}
4 changes: 4 additions & 0 deletions src/zone-unaware-iframe-message-bus.ts
Expand Up @@ -52,4 +52,8 @@ export class ZoneUnawareIFrameMessageBus extends MessageBus<Events> {
destroy() {
this._delegate.destroy();
}

getWindow(): Window {
return this._delegate.getWindow();
}
}

0 comments on commit 26fb1f4

Please sign in to comment.