From 3eec4badab6212ee44e44cb058563ec669cb01bd Mon Sep 17 00:00:00 2001 From: hawkgs Date: Wed, 25 Jun 2025 15:02:28 +0300 Subject: [PATCH] refactor(devtools): abstract and reuse the tree visualizer (#62264) Abstract the injector tree visualizer so it can be used for both the Injector Tree and Router Tree tabs without having to rely on separate identical implementations. PR Close #62264 --- .../dependency-injection/BUILD.bazel | 16 - .../injector-tree-visualizer.ts | 306 -- .../property-tab/property-view/BUILD.bazel | 3 +- .../dependency-viewer.component.ts | 2 +- .../resolution-path/BUILD.bazel | 2 +- .../resolution-path.component.html | 0 .../resolution-path.component.scss | 2 +- .../resolution-path.component.spec.ts | 2 +- .../resolution-path.component.ts | 2 +- .../devtools-tabs/injector-tree/BUILD.bazel | 7 +- .../injector-providers/BUILD.bazel | 1 - .../injector-tree/injector-tree-fns.spec.ts | 2651 +++++++++-------- .../injector-tree/injector-tree-fns.ts | 189 +- .../injector-tree/injector-tree.component.ts | 22 +- .../lib/devtools-tabs/router-tree/BUILD.bazel | 5 +- .../router-tree/router-tree-fns.spec.ts | 100 + .../router-tree/router-tree-fns.ts | 67 + .../router-tree/router-tree-visualizer.ts | 305 -- .../router-tree/router-tree.component.ts | 41 +- .../tree-visualizer-host/BUILD.bazel | 6 + .../tree-visualizer-host/graph-renderer.ts | 39 + .../tree-visualizer-host.component.scss | 88 +- .../tree-visualizer-host.component.ts | 0 .../tree-visualizer-host/tree-visualizer.ts | 246 ++ 24 files changed, 2017 insertions(+), 2085 deletions(-) delete mode 100644 devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/BUILD.bazel delete mode 100644 devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/injector-tree-visualizer.ts rename devtools/projects/ng-devtools/src/lib/devtools-tabs/{dependency-injection => directive-explorer/property-tab/property-view}/resolution-path/BUILD.bazel (95%) rename devtools/projects/ng-devtools/src/lib/devtools-tabs/{dependency-injection => directive-explorer/property-tab/property-view}/resolution-path/resolution-path.component.html (100%) rename devtools/projects/ng-devtools/src/lib/devtools-tabs/{dependency-injection => directive-explorer/property-tab/property-view}/resolution-path/resolution-path.component.scss (96%) rename devtools/projects/ng-devtools/src/lib/devtools-tabs/{dependency-injection => directive-explorer/property-tab/property-view}/resolution-path/resolution-path.component.spec.ts (96%) rename devtools/projects/ng-devtools/src/lib/devtools-tabs/{dependency-injection => directive-explorer/property-tab/property-view}/resolution-path/resolution-path.component.ts (92%) create mode 100644 devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-fns.spec.ts create mode 100644 devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-fns.ts delete mode 100644 devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-visualizer.ts rename devtools/projects/ng-devtools/src/lib/{devtools-tabs => shared}/tree-visualizer-host/BUILD.bazel (79%) create mode 100644 devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/graph-renderer.ts rename devtools/projects/ng-devtools/src/lib/{devtools-tabs => shared}/tree-visualizer-host/tree-visualizer-host.component.scss (56%) rename devtools/projects/ng-devtools/src/lib/{devtools-tabs => shared}/tree-visualizer-host/tree-visualizer-host.component.ts (100%) create mode 100644 devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/tree-visualizer.ts diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/BUILD.bazel deleted file mode 100644 index 3fb8a18e50f..00000000000 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/BUILD.bazel +++ /dev/null @@ -1,16 +0,0 @@ -load("//devtools/tools:typescript.bzl", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "injector_tree_visualizer", - srcs = ["injector-tree-visualizer.ts"], - interop_deps = [ - "//packages/core", - ], - deps = [ - "//:node_modules/@types/d3", - "//:node_modules/d3", - "//devtools/projects/protocol:protocol_rjs", - ], -) diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/injector-tree-visualizer.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/injector-tree-visualizer.ts deleted file mode 100644 index 7c80b1017f5..00000000000 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/injector-tree-visualizer.ts +++ /dev/null @@ -1,306 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import * as d3 from 'd3'; -import {SerializedInjector} from '../../../../../protocol'; - -let arrowDefId = 0; - -const injectorTypeToClassMap = new Map([ - ['imported-module', 'node-imported-module'], - ['environment', 'node-environment'], - ['element', 'node-element'], - ['null', 'node-null'], -]); - -export interface InjectorTreeNode { - injector: SerializedInjector; - children: InjectorTreeNode[]; -} - -export type InjectorTreeD3Node = d3.HierarchyPointNode; - -export abstract class GraphRenderer { - abstract render(graph: T): void; - abstract getNodeById(id: string): U | null; - abstract snapToNode(node: U): void; - abstract snapToRoot(): void; - abstract zoomScale(scale: number): void; - abstract root: U | null; - abstract get graphElement(): HTMLElement; - - protected nodeClickListeners: ((pointerEvent: PointerEvent, node: U) => void)[] = []; - protected nodeMouseoverListeners: ((pointerEvent: PointerEvent, node: U) => void)[] = []; - protected nodeMouseoutListeners: ((pointerEvent: PointerEvent, node: U) => void)[] = []; - - cleanup(): void { - this.nodeClickListeners = []; - this.nodeMouseoverListeners = []; - this.nodeMouseoutListeners = []; - } - - onNodeClick(cb: (pointerEvent: PointerEvent, node: U) => void): void { - this.nodeClickListeners.push(cb); - } - - onNodeMouseover(cb: (pointerEvent: PointerEvent, node: U) => void): void { - this.nodeMouseoverListeners.push(cb); - } - - onNodeMouseout(cb: (pointerEvent: PointerEvent, node: U) => void): void { - this.nodeMouseoutListeners.push(cb); - } -} - -interface InjectorTreeVisualizerConfig { - /** WARNING: For vertically-oriented trees, use separation greater than `1` */ - orientation: 'horizontal' | 'vertical'; - nodeSize: [width: number, height: number]; - nodeSeparation: (nodeA: InjectorTreeD3Node, nodeB: InjectorTreeD3Node) => number; - nodeLabelSize: [width: number, height: number]; -} - -export class InjectorTreeVisualizer extends GraphRenderer { - public config: InjectorTreeVisualizerConfig; - - constructor( - private _containerElement: HTMLElement, - private _graphElement: HTMLElement, - { - orientation = 'horizontal', - nodeSize = [200, 500], - nodeSeparation = () => 2, - nodeLabelSize = [250, 60], - }: Partial = {}, - ) { - super(); - - this.config = { - orientation, - nodeSize, - nodeSeparation, - nodeLabelSize, - }; - } - - private d3 = d3; - - override root: InjectorTreeD3Node | null = null; - zoomController: d3.ZoomBehavior | null = null; - - override zoomScale(scale: number) { - if (this.zoomController) { - this.zoomController.scaleTo( - this.d3.select(this._containerElement), - scale, - ); - } - } - - override snapToRoot(scale = 1): void { - if (this.root) { - this.snapToNode(this.root, scale); - } - } - - override snapToNode(node: InjectorTreeD3Node, scale = 1): void { - const svg = this.d3.select(this._containerElement); - const contHalfWidth = this._containerElement.clientWidth / 2; - const contHalfHeight = this._containerElement.clientHeight / 2; - const {x, y} = this.getNodeCoor(node); - - const t = d3.zoomIdentity - .translate(contHalfWidth, contHalfHeight) - .scale(scale) - .translate(-x, -y); - svg.transition().duration(500).call(this.zoomController!.transform, t); - } - - override get graphElement(): HTMLElement { - return this._graphElement; - } - - override getNodeById(id: string): InjectorTreeD3Node | null { - const selection = this.d3 - .select(this._containerElement) - .select(`.node[data-id="${id}"]`); - if (selection.empty()) { - return null; - } - return selection.datum(); - } - - override cleanup(): void { - super.cleanup(); - this.d3.select(this._graphElement).selectAll('*').remove(); - } - - override render(injectorGraph: InjectorTreeNode): void { - // cleanup old graph - this.cleanup(); - - const data = this.d3.hierarchy(injectorGraph, (node: InjectorTreeNode) => node.children); - const tree = this.d3.tree(); - const svg = this.d3.select(this._containerElement); - const g = this.d3.select(this._graphElement); - - this.zoomController = this.d3.zoom().scaleExtent([0.1, 2]); - this.zoomController.on('start zoom end', (e: {transform: number}) => { - g.attr('transform', e.transform); - }); - svg.call(this.zoomController); - - // Compute the new tree layout. - tree.nodeSize(this.config.nodeSize); - tree.separation((a: InjectorTreeD3Node, b: InjectorTreeD3Node) => { - return this.config.nodeSeparation(a, b); - }); - - const nodes = tree(data); - this.root = nodes; - - arrowDefId++; - svg - .append('svg:defs') - .selectAll('marker') - .data([`end${arrowDefId}`]) // Different link/path types can be defined here - .enter() - .append('svg:marker') // This section adds in the arrows - .attr('id', String) - .attr('viewBox', '0 -5 10 10') - .attr('refX', 10) - .attr('refY', 0) - .attr('class', 'arrow') - .attr('markerWidth', 6) - .attr('markerHeight', 6) - .attr('orient', 'auto') - .append('svg:path') - .attr('d', 'M0,-5L10,0L0,5'); - - const [labelWidth, labelHeight] = this.config.nodeLabelSize; - const halfLabelWidth = labelWidth / 2; - const halfLabelHeight = labelHeight / 2; - - g.selectAll('.link') - .data(nodes.descendants().slice(1)) - .enter() - .append('path') - .attr('class', (node: InjectorTreeD3Node) => { - const parentId = node.parent?.data?.injector?.id; - if (parentId === 'N/A') { - return 'link-hidden'; - } - - return `link`; - }) - .attr('data-id', (node: InjectorTreeD3Node) => { - const from = node.data.injector.id; - const to = node.parent?.data?.injector?.id; - - if (from && to) { - return `${from}-to-${to}`; - } - return ''; - }) - .attr('marker-end', `url(#end${arrowDefId})`) - .attr('d', (node: InjectorTreeD3Node) => { - const {x, y} = this.getNodeCoor(node); - const {x: parentX, y: parentY} = this.getNodeCoor(node.parent!); - - if (this.config.orientation === 'horizontal') { - return ` - M${x - halfLabelWidth},${y} - C${(x + parentX) / 2}, - ${y} ${(x + parentX) / 2}, - ${parentY} ${parentX + halfLabelWidth}, - ${parentY}`; - } - - return ` - M${x},${y - halfLabelHeight} - C${x}, - ${(y + parentY) / 2} ${parentX}, - ${(y + parentY) / 2} ${parentX}, - ${parentY + halfLabelHeight}`; - }); - - // Declare the nodes - const node = g - .selectAll('g.node') - .data(nodes.descendants()) - .enter() - .append('g') - .attr('class', (node: InjectorTreeD3Node) => { - if (node.data.injector.id === 'N/A') { - return 'node-hidden'; - } - return `node`; - }) - .attr('data-component-id', (node: InjectorTreeD3Node) => { - const injector = node.data.injector; - if (injector.type === 'element') { - return injector.node?.component?.id ?? -1; - } - return -1; - }) - .attr('data-id', (node: InjectorTreeD3Node) => { - return node.data.injector.id; - }) - .on('click', (pointerEvent: PointerEvent, node: InjectorTreeD3Node) => { - this.nodeClickListeners.forEach((listener) => listener(pointerEvent, node)); - }) - .on('mouseover', (pointerEvent: PointerEvent, node: InjectorTreeD3Node) => { - this.nodeMouseoverListeners.forEach((listener) => listener(pointerEvent, node)); - }) - .on('mouseout', (pointerEvent: PointerEvent, node: InjectorTreeD3Node) => { - this.nodeMouseoutListeners.forEach((listener) => listener(pointerEvent, node)); - }) - .attr('transform', (node: InjectorTreeD3Node) => { - const {x, y} = this.getNodeCoor(node); - return `translate(${x},${y})`; - }); - - node - .append('foreignObject') - .attr('width', labelWidth) - .attr('height', labelHeight) - .attr('x', -halfLabelWidth) - .attr('y', -halfLabelHeight) - .append('xhtml:div') - .attr('title', (node: InjectorTreeD3Node) => { - return node.data.injector.name; - }) - .attr('class', (node: InjectorTreeD3Node) => { - return [injectorTypeToClassMap.get(node.data?.injector?.type) ?? '', 'node-container'].join( - ' ', - ); - }) - .html((node: InjectorTreeD3Node) => { - const label = node.data.injector.name; - const lengthLimit = 25; - return label.length > lengthLimit - ? label.slice(0, lengthLimit - '...'.length) + '...' - : label; - }); - - svg.attr('height', '100%').attr('width', '100%'); - } - - /** Returns the node coordinates based on orientation. */ - private getNodeCoor(node: InjectorTreeD3Node): {x: number; y: number} { - const {x, y} = node; - - if (this.config.orientation === 'horizontal') { - return { - x: y, - y: x, - }; - } - return {x, y}; - } -} diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/BUILD.bazel index 214c65d4d9b..a0b84b3bf78 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/BUILD.bazel @@ -63,10 +63,9 @@ ng_project( "//:node_modules/rxjs", "//devtools/projects/ng-devtools/src/lib/application-environment:application-environment_rjs", "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager_rjs", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection:injector_tree_visualizer_rjs", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path:resolution-path_rjs", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest:index-forest_rjs", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver:property-resolver_rjs", + "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path:resolution-path_rjs", "//devtools/projects/protocol:protocol_rjs", ], ) diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/dependency-viewer.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/dependency-viewer.component.ts index 782d1448c9e..ff1fc5e55f3 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/dependency-viewer.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/dependency-viewer.component.ts @@ -8,7 +8,7 @@ import {Component, input} from '@angular/core'; import {SerializedInjectedService} from '../../../../../../../protocol'; -import {ResolutionPathComponent} from '../../../dependency-injection/resolution-path/resolution-path.component'; +import {ResolutionPathComponent} from './resolution-path/resolution-path.component'; import {MatTooltip} from '@angular/material/tooltip'; import {MatExpansionModule} from '@angular/material/expansion'; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/BUILD.bazel similarity index 95% rename from devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/BUILD.bazel rename to devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/BUILD.bazel index d32b3a9b8d0..df5f3313e30 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/BUILD.bazel @@ -2,7 +2,7 @@ load("//devtools/tools:ng_project.bzl", "ng_project") load("//devtools/tools:typescript.bzl", "ts_test_library") load("//tools:defaults2.bzl", "ng_web_test_suite", "sass_binary") -package(default_visibility = ["//:__subpackages__"]) +package(default_visibility = ["//devtools:__subpackages__"]) sass_binary( name = "resolution_path_styles", diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/resolution-path.component.html b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/resolution-path.component.html similarity index 100% rename from devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/resolution-path.component.html rename to devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/resolution-path.component.html diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/resolution-path.component.scss b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/resolution-path.component.scss similarity index 96% rename from devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/resolution-path.component.scss rename to devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/resolution-path.component.scss index 7b72a2d8ed2..9bcaea8dada 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/resolution-path.component.scss +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/resolution-path.component.scss @@ -1,4 +1,4 @@ -@use '../../../../styles/typography'; +@use '../../../../../../styles/typography'; :host { display: flex; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/resolution-path.component.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/resolution-path.component.spec.ts similarity index 96% rename from devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/resolution-path.component.spec.ts rename to devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/resolution-path.component.spec.ts index 892f03d2f66..99bbf905d11 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/resolution-path.component.spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/resolution-path.component.spec.ts @@ -10,7 +10,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {NODE_TYPE_CLASS_MAP, ResolutionPathComponent} from './resolution-path.component'; -import {SerializedInjector} from '../../../../../../protocol'; +import {SerializedInjector} from '../../../../../../../../protocol'; describe('ResolutionPath', () => { let component: ResolutionPathComponent; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/resolution-path.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/resolution-path.component.ts similarity index 92% rename from devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/resolution-path.component.ts rename to devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/resolution-path.component.ts index 721cc374f9c..d6d1a1fe280 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path/resolution-path.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path/resolution-path.component.ts @@ -7,7 +7,7 @@ */ import {Component, computed, input} from '@angular/core'; -import {SerializedInjector} from '../../../../../../protocol'; +import {SerializedInjector} from '../../../../../../../../protocol'; export const NODE_TYPE_CLASS_MAP: {[key in SerializedInjector['type']]: string} = { 'element': 'type-element', diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/BUILD.bazel index 9d0294e2e7d..ce63dc9e39f 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/BUILD.bazel @@ -32,11 +32,9 @@ ng_project( "//:node_modules/@types/d3", "//:node_modules/d3", "//:node_modules/rxjs", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection:injector_tree_visualizer_rjs", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path:resolution-path_rjs", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-providers:injector-providers_rjs", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/tree-visualizer-host:tree-visualizer-host_rjs", "//devtools/projects/ng-devtools/src/lib/shared/responsive-split:responsive-split_rjs", + "//devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host:tree-visualizer-host_rjs", "//devtools/projects/ng-devtools/src/lib/vendor/angular-split:angular-split_rjs", "//devtools/projects/protocol:protocol_rjs", ], @@ -56,7 +54,6 @@ ts_test_library( ], deps = [ ":injector_tree_fns_rjs", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection:injector_tree_visualizer_rjs", "//devtools/projects/protocol:protocol_rjs", ], ) @@ -65,7 +62,7 @@ ts_project( name = "injector_tree_fns", srcs = ["injector-tree-fns.ts"], deps = [ - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection:injector_tree_visualizer_rjs", + "//devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host:tree-visualizer-host_rjs", "//devtools/projects/protocol:protocol_rjs", ], ) diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-providers/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-providers/BUILD.bazel index 17de7ec9d9c..02c347e2d16 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-providers/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-providers/BUILD.bazel @@ -26,7 +26,6 @@ ng_project( ], deps = [ "//:node_modules/@angular/material", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path:resolution-path_rjs", "//devtools/projects/protocol:protocol_rjs", ], ) diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree-fns.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree-fns.spec.ts index 79ee4e99f83..93797858d4f 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree-fns.spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree-fns.spec.ts @@ -7,10 +7,6 @@ */ import {DevToolsNode, SerializedInjector} from '../../../../../protocol'; -import { - InjectorTreeD3Node, - InjectorTreeNode, -} from '../dependency-injection/injector-tree-visualizer'; import { equalInjector, @@ -18,6 +14,8 @@ import { getInjectorIdsToRootFromNode, grabInjectorPathsFromDirectiveForest, InjectorPath, + InjectorTreeD3Node, + InjectorTreeNode, splitInjectorPathsIntoElementAndEnvironmentPaths, transformInjectorResolutionPathsIntoTree, } from './injector-tree-fns'; @@ -26,17 +24,24 @@ describe('getInjectorIdsToRootFromNode', () => { it('should be able to get ids from a node', () => { const root = { data: { + id: '1', + label: 'root', + type: 'environment', injector: { id: '1', name: 'root', type: 'environment', }, + children: [], }, }; const child = { parent: root, data: { + id: '2', + label: 'child', + type: 'environment', injector: { id: '2', name: 'child', @@ -48,17 +53,25 @@ describe('getInjectorIdsToRootFromNode', () => { const grandChild = { parent: child, data: { + id: '3', + label: 'grand child', + type: 'environment', injector: { id: '3', name: 'grand child', type: 'environment', }, + children: [], }, }; - expect(getInjectorIdsToRootFromNode(root as InjectorTreeD3Node)).toEqual(['1']); - expect(getInjectorIdsToRootFromNode(child as InjectorTreeD3Node)).toEqual(['2', '1']); - expect(getInjectorIdsToRootFromNode(grandChild as InjectorTreeD3Node)).toEqual(['3', '2', '1']); + expect(getInjectorIdsToRootFromNode(root as any as InjectorTreeD3Node)).toEqual(['1']); + expect(getInjectorIdsToRootFromNode(child as any as InjectorTreeD3Node)).toEqual(['2', '1']); + expect(getInjectorIdsToRootFromNode(grandChild as any as InjectorTreeD3Node)).toEqual([ + '3', + '2', + '1', + ]); }); }); @@ -1892,1509 +1905,1518 @@ describe('transformInjectorResolutionPathsIntoTree', () => { ]; const expected: InjectorTreeNode = { - 'injector': {'name': '', 'type': 'hidden', 'id': 'N/A'}, - 'children': [ + label: '', + injector: {name: '', type: 'hidden', id: 'N/A'}, + children: [ { - 'injector': { - 'id': '1', - 'type': 'element', - 'name': '_AppComponent', - 'node': { - 'element': 'app-root', - 'hydration': null, - 'defer': null, - 'component': {'name': 'app-root', 'isElement': false, 'id': 0}, - 'directives': [], - 'children': [ + label: '_AppComponent', + injector: { + id: '1', + type: 'element', + name: '_AppComponent', + node: { + element: 'app-root', + hydration: null, + defer: null, + component: {name: 'app-root', isElement: false, id: 0}, + directives: [], + children: [ { - 'element': 'router-outlet', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterOutlet', 'id': 1}], - 'children': [], - 'resolutionPath': [ - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'router-outlet', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterOutlet', id: 1}], + children: [], + resolutionPath: [ + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-demo-component', - 'component': {'name': 'app-demo-component', 'isElement': false, 'id': 2}, - 'directives': [], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-demo-component', + component: {name: 'app-demo-component', isElement: false, id: 2}, + directives: [], + hydration: null, + defer: null, + children: [ { - 'element': 'router-outlet', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterOutlet', 'id': 3}], - 'children': [], - 'resolutionPath': [ - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'router-outlet', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterOutlet', id: 3}], + children: [], + resolutionPath: [ + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todo-demo', - 'component': {'name': 'app-todo-demo', 'isElement': false, 'id': 4}, - 'directives': [], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todo-demo', + component: {name: 'app-todo-demo', isElement: false, id: 4}, + directives: [], + hydration: null, + defer: null, + children: [ { - 'element': 'a', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterLink', 'id': 5}], - 'children': [], - 'resolutionPath': [ - {'id': '8', 'type': 'element', 'name': '_RouterLink'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterLink', id: 5}], + children: [], + resolutionPath: [ + {id: '8', type: 'element', name: '_RouterLink'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'a', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterLink', 'id': 6}], - 'children': [], - 'resolutionPath': [ - {'id': '11', 'type': 'element', 'name': '_RouterLink'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterLink', id: 6}], + children: [], + resolutionPath: [ + {id: '11', type: 'element', name: '_RouterLink'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'router-outlet', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterOutlet', 'id': 7}], - 'children': [], - 'resolutionPath': [ - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'router-outlet', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterOutlet', id: 7}], + children: [], + resolutionPath: [ + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todos', - 'component': {'name': 'app-todos', 'isElement': false, 'id': 8}, - 'directives': [], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todos', + component: {name: 'app-todos', isElement: false, id: 8}, + directives: [], + hydration: null, + defer: null, + children: [ { - 'element': 'a', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterLink', 'id': 9}], - 'children': [], - 'resolutionPath': [ - {'id': '13', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterLink', id: 9}], + children: [], + resolutionPath: [ + {id: '13', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'a', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterLink', 'id': 10}], - 'children': [], - 'resolutionPath': [ - {'id': '16', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterLink', id: 10}], + children: [], + resolutionPath: [ + {id: '16', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'a', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterLink', 'id': 11}], - 'children': [], - 'resolutionPath': [ - {'id': '17', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterLink', id: 11}], + children: [], + resolutionPath: [ + {id: '17', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todo', - 'component': {'name': 'app-todo', 'isElement': false, 'id': 12}, - 'directives': [{'name': '_TooltipDirective', 'id': 13}], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todo', + component: {name: 'app-todo', isElement: false, id: 12}, + directives: [{name: '_TooltipDirective', id: 13}], + hydration: null, + defer: null, + children: [ { - 'element': 'div', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_TooltipDirective', 'id': 14}], - 'children': [], - 'resolutionPath': [ - {'id': '18', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '19', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'div', + component: null, + hydration: null, + defer: null, + directives: [{name: '_TooltipDirective', id: 14}], + children: [], + resolutionPath: [ + {id: '18', type: 'element', name: '_TooltipDirective'}, + {id: '19', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '19', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '19', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todo', - 'component': {'name': 'app-todo', 'isElement': false, 'id': 15}, - 'directives': [{'name': '_TooltipDirective', 'id': 16}], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todo', + component: {name: 'app-todo', isElement: false, id: 15}, + directives: [{name: '_TooltipDirective', id: 16}], + hydration: null, + defer: null, + children: [ { - 'element': 'div', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_TooltipDirective', 'id': 17}], - 'children': [], - 'resolutionPath': [ - {'id': '21', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '22', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'div', + component: null, + hydration: null, + defer: null, + directives: [{name: '_TooltipDirective', id: 17}], + children: [], + resolutionPath: [ + {id: '21', type: 'element', name: '_TooltipDirective'}, + {id: '22', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '22', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '22', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': '#comment', - 'component': null, - 'directives': [{'name': '_NgForOf', 'id': 18}], - 'hydration': null, - 'defer': null, - 'children': [], - 'resolutionPath': [ - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: '#comment', + component: null, + directives: [{name: '_NgForOf', id: 18}], + hydration: null, + defer: null, + children: [], + resolutionPath: [ + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-heavy', - 'component': {'name': 'app-heavy', 'isElement': false, 'id': 20}, - 'directives': [], - 'children': [], - 'hydration': null, - 'defer': null, - 'resolutionPath': [ - {'id': '24', 'type': 'element', 'name': '_HeavyComponent'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'app-heavy', + component: {name: 'app-heavy', isElement: false, id: 20}, + directives: [], + children: [], + hydration: null, + defer: null, + resolutionPath: [ + {id: '24', type: 'element', name: '_HeavyComponent'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, }, - 'children': [ + children: [ { - 'injector': { - 'id': '6', - 'type': 'element', - 'name': '_DemoAppComponent', - 'node': { - 'element': 'app-demo-component', - 'component': {'name': 'app-demo-component', 'isElement': false, 'id': 2}, - 'directives': [], - 'hydration': null, - 'defer': null, - 'children': [ + label: '_DemoAppComponent', + injector: { + id: '6', + type: 'element', + name: '_DemoAppComponent', + node: { + element: 'app-demo-component', + component: {name: 'app-demo-component', isElement: false, id: 2}, + directives: [], + hydration: null, + defer: null, + children: [ { - 'element': 'router-outlet', - 'component': null, - 'directives': [{'name': '_RouterOutlet', 'id': 3}], - 'children': [], - 'hydration': null, - 'defer': null, - 'resolutionPath': [ - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'router-outlet', + component: null, + directives: [{name: '_RouterOutlet', id: 3}], + children: [], + hydration: null, + defer: null, + resolutionPath: [ + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todo-demo', - 'component': {'name': 'app-todo-demo', 'isElement': false, 'id': 4}, - 'directives': [], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todo-demo', + component: {name: 'app-todo-demo', isElement: false, id: 4}, + directives: [], + hydration: null, + defer: null, + children: [ { - 'element': 'a', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterLink', 'id': 5}], - 'children': [], - 'resolutionPath': [ - {'id': '8', 'type': 'element', 'name': '_RouterLink'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterLink', id: 5}], + children: [], + resolutionPath: [ + {id: '8', type: 'element', name: '_RouterLink'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'a', - 'component': null, - 'directives': [{'name': '_RouterLink', 'id': 6}], - 'children': [], - 'hydration': null, - 'defer': null, - 'resolutionPath': [ - {'id': '11', 'type': 'element', 'name': '_RouterLink'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + directives: [{name: '_RouterLink', id: 6}], + children: [], + hydration: null, + defer: null, + resolutionPath: [ + {id: '11', type: 'element', name: '_RouterLink'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'router-outlet', - 'component': null, - 'directives': [{'name': '_RouterOutlet', 'id': 7}], - 'children': [], - 'hydration': null, - 'defer': null, - 'resolutionPath': [ - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'router-outlet', + component: null, + directives: [{name: '_RouterOutlet', id: 7}], + children: [], + hydration: null, + defer: null, + resolutionPath: [ + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todos', - 'component': {'name': 'app-todos', 'isElement': false, 'id': 8}, - 'directives': [], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todos', + component: {name: 'app-todos', isElement: false, id: 8}, + directives: [], + hydration: null, + defer: null, + children: [ { - 'element': 'a', - 'component': null, - 'directives': [{'name': '_RouterLink', 'id': 9}], - 'children': [], - 'hydration': null, - 'defer': null, - 'resolutionPath': [ - {'id': '13', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + directives: [{name: '_RouterLink', id: 9}], + children: [], + hydration: null, + defer: null, + resolutionPath: [ + {id: '13', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'a', - 'component': null, - 'directives': [{'name': '_RouterLink', 'id': 10}], - 'children': [], - 'hydration': null, - 'defer': null, - 'resolutionPath': [ - {'id': '16', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + directives: [{name: '_RouterLink', id: 10}], + children: [], + hydration: null, + defer: null, + resolutionPath: [ + {id: '16', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'a', - 'component': null, - 'directives': [{'name': '_RouterLink', 'id': 11}], - 'children': [], - 'hydration': null, - 'defer': null, - 'resolutionPath': [ - {'id': '17', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + directives: [{name: '_RouterLink', id: 11}], + children: [], + hydration: null, + defer: null, + resolutionPath: [ + {id: '17', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todo', - 'component': {'name': 'app-todo', 'isElement': false, 'id': 12}, - 'directives': [{'name': '_TooltipDirective', 'id': 13}], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todo', + component: {name: 'app-todo', isElement: false, id: 12}, + directives: [{name: '_TooltipDirective', id: 13}], + hydration: null, + defer: null, + children: [ { - 'element': 'div', - 'component': null, - 'directives': [{'name': '_TooltipDirective', 'id': 14}], - 'children': [], - 'hydration': null, - 'defer': null, - 'resolutionPath': [ - {'id': '18', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '19', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'div', + component: null, + directives: [{name: '_TooltipDirective', id: 14}], + children: [], + hydration: null, + defer: null, + resolutionPath: [ + {id: '18', type: 'element', name: '_TooltipDirective'}, + {id: '19', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '19', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '19', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todo', - 'component': {'name': 'app-todo', 'isElement': false, 'id': 15}, - 'directives': [{'name': '_TooltipDirective', 'id': 16}], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todo', + component: {name: 'app-todo', isElement: false, id: 15}, + directives: [{name: '_TooltipDirective', id: 16}], + hydration: null, + defer: null, + children: [ { - 'element': 'div', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_TooltipDirective', 'id': 17}], - 'children': [], - 'resolutionPath': [ - {'id': '21', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '22', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'div', + component: null, + hydration: null, + defer: null, + directives: [{name: '_TooltipDirective', id: 17}], + children: [], + resolutionPath: [ + {id: '21', type: 'element', name: '_TooltipDirective'}, + {id: '22', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '22', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '22', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': '#comment', - 'component': null, - 'directives': [{'name': '_NgForOf', 'id': 18}], - 'children': [], - 'hydration': null, - 'defer': null, - 'resolutionPath': [ - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: '#comment', + component: null, + directives: [{name: '_NgForOf', id: 18}], + children: [], + hydration: null, + defer: null, + resolutionPath: [ + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-heavy', - 'component': {'name': 'app-heavy', 'isElement': false, 'id': 20}, - 'hydration': null, - 'defer': null, - 'directives': [], - 'children': [], - 'resolutionPath': [ - {'id': '24', 'type': 'element', 'name': '_HeavyComponent'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'app-heavy', + component: {name: 'app-heavy', isElement: false, id: 20}, + hydration: null, + defer: null, + directives: [], + children: [], + resolutionPath: [ + {id: '24', type: 'element', name: '_HeavyComponent'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, }, - 'children': [ + children: [ { - 'injector': { - 'id': '9', - 'type': 'element', - 'name': '_AppTodoComponent', - 'node': { - 'element': 'app-todo-demo', - 'component': {'name': 'app-todo-demo', 'isElement': false, 'id': 4}, - 'hydration': null, - 'defer': null, - 'directives': [], - 'children': [ + label: '_AppTodoComponent', + injector: { + id: '9', + type: 'element', + name: '_AppTodoComponent', + node: { + element: 'app-todo-demo', + component: {name: 'app-todo-demo', isElement: false, id: 4}, + hydration: null, + defer: null, + directives: [], + children: [ { - 'element': 'a', - 'component': null, - 'directives': [{'name': '_RouterLink', 'id': 5}], - 'children': [], - 'hydration': null, - 'defer': null, - 'resolutionPath': [ - {'id': '8', 'type': 'element', 'name': '_RouterLink'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + directives: [{name: '_RouterLink', id: 5}], + children: [], + hydration: null, + defer: null, + resolutionPath: [ + {id: '8', type: 'element', name: '_RouterLink'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'a', - 'component': null, - 'directives': [{'name': '_RouterLink', 'id': 6}], - 'children': [], - 'hydration': null, - 'defer': null, - 'resolutionPath': [ - {'id': '11', 'type': 'element', 'name': '_RouterLink'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + directives: [{name: '_RouterLink', id: 6}], + children: [], + hydration: null, + defer: null, + resolutionPath: [ + {id: '11', type: 'element', name: '_RouterLink'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'router-outlet', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterOutlet', 'id': 7}], - 'children': [], - 'resolutionPath': [ - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'router-outlet', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterOutlet', id: 7}], + children: [], + resolutionPath: [ + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todos', - 'component': {'name': 'app-todos', 'isElement': false, 'id': 8}, - 'directives': [], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todos', + component: {name: 'app-todos', isElement: false, id: 8}, + directives: [], + hydration: null, + defer: null, + children: [ { - 'element': 'a', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterLink', 'id': 9}], - 'children': [], - 'resolutionPath': [ - {'id': '13', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterLink', id: 9}], + children: [], + resolutionPath: [ + {id: '13', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'a', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterLink', 'id': 10}], - 'children': [], - 'resolutionPath': [ - {'id': '16', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterLink', id: 10}], + children: [], + resolutionPath: [ + {id: '16', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'a', - 'component': null, - 'directives': [{'name': '_RouterLink', 'id': 11}], - 'hydration': null, - 'defer': null, - 'children': [], - 'resolutionPath': [ - {'id': '17', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + directives: [{name: '_RouterLink', id: 11}], + hydration: null, + defer: null, + children: [], + resolutionPath: [ + {id: '17', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todo', - 'component': {'name': 'app-todo', 'isElement': false, 'id': 12}, - 'directives': [{'name': '_TooltipDirective', 'id': 13}], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todo', + component: {name: 'app-todo', isElement: false, id: 12}, + directives: [{name: '_TooltipDirective', id: 13}], + hydration: null, + defer: null, + children: [ { - 'element': 'div', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_TooltipDirective', 'id': 14}], - 'children': [], - 'resolutionPath': [ - {'id': '18', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '19', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'div', + component: null, + hydration: null, + defer: null, + directives: [{name: '_TooltipDirective', id: 14}], + children: [], + resolutionPath: [ + {id: '18', type: 'element', name: '_TooltipDirective'}, + {id: '19', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '19', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '19', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todo', - 'component': {'name': 'app-todo', 'isElement': false, 'id': 15}, - 'directives': [{'name': '_TooltipDirective', 'id': 16}], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todo', + component: {name: 'app-todo', isElement: false, id: 15}, + directives: [{name: '_TooltipDirective', id: 16}], + hydration: null, + defer: null, + children: [ { - 'element': 'div', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_TooltipDirective', 'id': 17}], - 'children': [], - 'resolutionPath': [ - {'id': '21', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '22', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'div', + component: null, + hydration: null, + defer: null, + directives: [{name: '_TooltipDirective', id: 17}], + children: [], + resolutionPath: [ + {id: '21', type: 'element', name: '_TooltipDirective'}, + {id: '22', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '22', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '22', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': '#comment', - 'component': null, - 'directives': [{'name': '_NgForOf', 'id': 18}], - 'hydration': null, - 'defer': null, - 'children': [], - 'resolutionPath': [ - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: '#comment', + component: null, + directives: [{name: '_NgForOf', id: 18}], + hydration: null, + defer: null, + children: [], + resolutionPath: [ + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, }, - 'children': [ + children: [ { - 'injector': { - 'id': '14', - 'type': 'element', - 'name': '_TodosComponent', - 'node': { - 'element': 'app-todos', - 'hydration': null, - 'defer': null, - 'component': {'name': 'app-todos', 'isElement': false, 'id': 8}, - 'directives': [], - 'children': [ + label: '_TodosComponent', + injector: { + id: '14', + type: 'element', + name: '_TodosComponent', + node: { + element: 'app-todos', + hydration: null, + defer: null, + component: {name: 'app-todos', isElement: false, id: 8}, + directives: [], + children: [ { - 'element': 'a', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterLink', 'id': 9}], - 'children': [], - 'resolutionPath': [ - {'id': '13', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterLink', id: 9}], + children: [], + resolutionPath: [ + {id: '13', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'a', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterLink', 'id': 10}], - 'children': [], - 'resolutionPath': [ - {'id': '16', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterLink', id: 10}], + children: [], + resolutionPath: [ + {id: '16', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'a', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_RouterLink', 'id': 11}], - 'children': [], - 'resolutionPath': [ - {'id': '17', 'type': 'element', 'name': '_RouterLink'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'a', + component: null, + hydration: null, + defer: null, + directives: [{name: '_RouterLink', id: 11}], + children: [], + resolutionPath: [ + {id: '17', type: 'element', name: '_RouterLink'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todo', - 'component': {'name': 'app-todo', 'isElement': false, 'id': 12}, - 'directives': [{'name': '_TooltipDirective', 'id': 13}], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todo', + component: {name: 'app-todo', isElement: false, id: 12}, + directives: [{name: '_TooltipDirective', id: 13}], + hydration: null, + defer: null, + children: [ { - 'element': 'div', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_TooltipDirective', 'id': 14}], - 'children': [], - 'resolutionPath': [ - {'id': '18', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '19', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'div', + component: null, + hydration: null, + defer: null, + directives: [{name: '_TooltipDirective', id: 14}], + children: [], + resolutionPath: [ + {id: '18', type: 'element', name: '_TooltipDirective'}, + {id: '19', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '19', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '19', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': 'app-todo', - 'component': {'name': 'app-todo', 'isElement': false, 'id': 15}, - 'directives': [{'name': '_TooltipDirective', 'id': 16}], - 'hydration': null, - 'defer': null, - 'children': [ + element: 'app-todo', + component: {name: 'app-todo', isElement: false, id: 15}, + directives: [{name: '_TooltipDirective', id: 16}], + hydration: null, + defer: null, + children: [ { - 'element': 'div', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_TooltipDirective', 'id': 17}], - 'children': [], - 'resolutionPath': [ - {'id': '21', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '22', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'div', + component: null, + hydration: null, + defer: null, + directives: [{name: '_TooltipDirective', id: 17}], + children: [], + resolutionPath: [ + {id: '21', type: 'element', name: '_TooltipDirective'}, + {id: '22', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '22', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '22', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, { - 'element': '#comment', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_NgForOf', 'id': 18}], - 'children': [], - 'resolutionPath': [ - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: '#comment', + component: null, + hydration: null, + defer: null, + directives: [{name: '_NgForOf', id: 18}], + children: [], + resolutionPath: [ + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, }, - 'children': [ + children: [ { - 'injector': { - 'id': '19', - 'type': 'element', - 'name': '_TodoComponent', - 'node': { - 'element': 'app-todo', - 'hydration': null, - 'defer': null, - 'component': {'name': 'app-todo', 'isElement': false, 'id': 12}, - 'directives': [{'name': '_TooltipDirective', 'id': 13}], - 'children': [ + label: '_TodoComponent', + injector: { + id: '19', + type: 'element', + name: '_TodoComponent', + node: { + element: 'app-todo', + hydration: null, + defer: null, + component: {name: 'app-todo', isElement: false, id: 12}, + directives: [{name: '_TooltipDirective', id: 13}], + children: [ { - 'element': 'div', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_TooltipDirective', 'id': 14}], - 'children': [], - 'resolutionPath': [ - {'id': '18', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '19', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'div', + component: null, + hydration: null, + defer: null, + directives: [{name: '_TooltipDirective', id: 14}], + children: [], + resolutionPath: [ + {id: '18', type: 'element', name: '_TooltipDirective'}, + {id: '19', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '19', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '19', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, }, - 'children': [ + children: [ { - 'injector': { - 'id': '18', - 'type': 'element', - 'name': '_TooltipDirective', - 'node': { - 'element': 'div', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_TooltipDirective', 'id': 14}], - 'children': [], - 'resolutionPath': [ - {'id': '18', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '19', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + label: '_TooltipDirective', + injector: { + id: '18', + type: 'element', + name: '_TooltipDirective', + node: { + element: 'div', + component: null, + hydration: null, + defer: null, + directives: [{name: '_TooltipDirective', id: 14}], + children: [], + resolutionPath: [ + {id: '18', type: 'element', name: '_TooltipDirective'}, + {id: '19', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, }, - 'children': [], + children: [], }, ], }, { - 'injector': { - 'id': '22', - 'type': 'element', - 'name': '_TodoComponent', - 'node': { - 'element': 'app-todo', - 'hydration': null, - 'defer': null, - 'component': {'name': 'app-todo', 'isElement': false, 'id': 15}, - 'directives': [{'name': '_TooltipDirective', 'id': 16}], - 'children': [ + label: '_TodoComponent', + injector: { + id: '22', + type: 'element', + name: '_TodoComponent', + node: { + element: 'app-todo', + hydration: null, + defer: null, + component: {name: 'app-todo', isElement: false, id: 15}, + directives: [{name: '_TooltipDirective', id: 16}], + children: [ { - 'element': 'div', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_TooltipDirective', 'id': 17}], - 'children': [], - 'resolutionPath': [ - {'id': '21', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '22', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + element: 'div', + component: null, + hydration: null, + defer: null, + directives: [{name: '_TooltipDirective', id: 17}], + children: [], + resolutionPath: [ + {id: '21', type: 'element', name: '_TooltipDirective'}, + {id: '22', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, ], - 'resolutionPath': [ - {'id': '22', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + resolutionPath: [ + {id: '22', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, }, - 'children': [ + children: [ { - 'injector': { - 'id': '21', - 'type': 'element', - 'name': '_TooltipDirective', - 'node': { - 'element': 'div', - 'component': null, - 'hydration': null, - 'defer': null, - 'directives': [{'name': '_TooltipDirective', 'id': 17}], - 'children': [], - 'resolutionPath': [ - {'id': '21', 'type': 'element', 'name': '_TooltipDirective'}, - {'id': '22', 'type': 'element', 'name': '_TodoComponent'}, - {'id': '20', 'type': 'element', 'name': '_NgForOf'}, - {'id': '14', 'type': 'element', 'name': '_TodosComponent'}, - {'id': '12', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '9', 'type': 'element', 'name': '_AppTodoComponent'}, - {'id': '5', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '15', 'type': 'environment', 'name': '_HomeModule'}, - {'id': '10', 'type': 'environment', 'name': '_AppModule'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + label: '_TooltipDirective', + injector: { + id: '21', + type: 'element', + name: '_TooltipDirective', + node: { + element: 'div', + component: null, + hydration: null, + defer: null, + directives: [{name: '_TooltipDirective', id: 17}], + children: [], + resolutionPath: [ + {id: '21', type: 'element', name: '_TooltipDirective'}, + {id: '22', type: 'element', name: '_TodoComponent'}, + {id: '20', type: 'element', name: '_NgForOf'}, + {id: '14', type: 'element', name: '_TodosComponent'}, + {id: '12', type: 'element', name: '_RouterOutlet'}, + {id: '9', type: 'element', name: '_AppTodoComponent'}, + {id: '5', type: 'element', name: '_RouterOutlet'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '15', type: 'environment', name: '_HomeModule'}, + {id: '10', type: 'environment', name: '_AppModule'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, }, - 'children': [], + children: [], }, ], }, @@ -3403,30 +3425,31 @@ describe('transformInjectorResolutionPathsIntoTree', () => { ], }, { - 'injector': { - 'id': '24', - 'type': 'element', - 'name': '_HeavyComponent', - 'node': { - 'element': 'app-heavy', - 'hydration': null, - 'defer': null, - 'component': {'name': 'app-heavy', 'isElement': false, 'id': 20}, - 'directives': [], - 'children': [], - 'resolutionPath': [ - {'id': '24', 'type': 'element', 'name': '_HeavyComponent'}, - {'id': '6', 'type': 'element', 'name': '_DemoAppComponent'}, - {'id': '0', 'type': 'element', 'name': '_RouterOutlet'}, - {'id': '1', 'type': 'element', 'name': '_AppComponent'}, - {'id': '7', 'type': 'environment', 'name': '_DemoAppModule'}, - {'id': '2', 'type': 'environment', 'name': '_AppModule'}, - {'id': '3', 'type': 'environment', 'name': 'Platform: core'}, - {'id': '4', 'type': 'null', 'name': 'Null Injector'}, + label: '_HeavyComponent', + injector: { + id: '24', + type: 'element', + name: '_HeavyComponent', + node: { + element: 'app-heavy', + hydration: null, + defer: null, + component: {name: 'app-heavy', isElement: false, id: 20}, + directives: [], + children: [], + resolutionPath: [ + {id: '24', type: 'element', name: '_HeavyComponent'}, + {id: '6', type: 'element', name: '_DemoAppComponent'}, + {id: '0', type: 'element', name: '_RouterOutlet'}, + {id: '1', type: 'element', name: '_AppComponent'}, + {id: '7', type: 'environment', name: '_DemoAppModule'}, + {id: '2', type: 'environment', name: '_AppModule'}, + {id: '3', type: 'environment', name: 'Platform: core'}, + {id: '4', type: 'null', name: 'Null Injector'}, ], }, }, - 'children': [], + children: [], }, ], }, diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree-fns.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree-fns.ts index 1fa78a22590..634c358538b 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree-fns.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree-fns.ts @@ -7,17 +7,95 @@ */ import {DevToolsNode, SerializedInjector} from '../../../../../protocol'; - import { - InjectorTreeD3Node, - InjectorTreeNode, -} from '../dependency-injection/injector-tree-visualizer'; + SvgD3Link, + SvgD3Node, + TreeD3Node, + TreeNode, + TreeVisualizer, +} from '../../shared/tree-visualizer-host/tree-visualizer'; + +// Types export interface InjectorPath { node: DevToolsNode; path: SerializedInjector[]; } +export type InjectorTreeVisualizer = TreeVisualizer; + +export interface InjectorTreeNode extends TreeNode { + injector: SerializedInjector; + children: InjectorTreeNode[]; +} + +export type InjectorTreeD3Node = TreeD3Node; + +// Consts + +const ANGULAR_DIRECTIVES = [ + 'NgClass', + 'NgComponentOutlet', + 'NgFor', + 'NgForOf', + 'NgIf', + 'NgOptimizedImage', + 'NgPlural', + 'NgPluralCase', + 'NgStyle', + 'NgSwitch', + 'NgSwitchCase', + 'NgSwitchDefault', + 'NgTemplateOutlet', + 'AbstractFormGroupDirective', + 'CheckboxControlValueAccessor', + 'CheckboxRequiredValidator', + 'DefaultValueAccessor', + 'EmailValidator', + 'FormArrayName', + 'FormControlDirective', + 'FormControlName', + 'FormGroupDirective', + 'FormGroupName', + 'MaxLengthValidator', + 'MaxValidator', + 'MinLengthValidator', + 'MinValidator', + 'NgControlStatus', + 'NgControlStatusGroup', + 'NgForm', + 'NgModel', + 'NgModelGroup', + 'NgSelectOption', + 'NumberValueAccessor', + 'PatternValidator', + 'RadioControlValueAccessor', + 'RangeValueAccessor', + 'RequiredValidator', + 'SelectControlValueAccessor', + 'SelectMultipleControlValueAccessor', + 'RouterLink', + 'RouterLinkActive', + 'RouterLinkWithHref', + 'RouterOutlet', + 'UpgradeComponent', +]; + +const IGNORED_ANGULAR_INJECTORS = new Set([ + 'Null Injector', + ...ANGULAR_DIRECTIVES, + ...ANGULAR_DIRECTIVES.map((directive) => `_${directive}`), +]); + +const INJECTOR_TYPE_CLASS_MAP = new Map([ + ['imported-module', 'node-imported-module'], + ['environment', 'node-environment'], + ['element', 'node-element'], + ['null', 'node-null'], +]); + +// Functions + export function getInjectorIdsToRootFromNode(node: InjectorTreeD3Node): string[] { const ids: string[] = []; let currentNode = node; @@ -71,8 +149,9 @@ export function transformInjectorResolutionPathsIntoTree( continue; } - const next = { - injector: injector, + const next: InjectorTreeNode = { + label: injector.name || '', + injector, children: [], }; next.injector.node = injectorIdToNode.get(next.injector.id); @@ -81,12 +160,11 @@ export function transformInjectorResolutionPathsIntoTree( } } - const hiddenRoot = { + return { + label: '', injector: {name: '', type: 'hidden', id: 'N/A'}, children: injectorTree, }; - - return hiddenRoot as any; } export function grabInjectorPathsFromDirectiveForest( @@ -160,60 +238,6 @@ export function splitInjectorPathsIntoElementAndEnvironmentPaths(injectorPaths: }; } -const ANGULAR_DIRECTIVES = [ - 'NgClass', - 'NgComponentOutlet', - 'NgFor', - 'NgForOf', - 'NgIf', - 'NgOptimizedImage', - 'NgPlural', - 'NgPluralCase', - 'NgStyle', - 'NgSwitch', - 'NgSwitchCase', - 'NgSwitchDefault', - 'NgTemplateOutlet', - 'AbstractFormGroupDirective', - 'CheckboxControlValueAccessor', - 'CheckboxRequiredValidator', - 'DefaultValueAccessor', - 'EmailValidator', - 'FormArrayName', - 'FormControlDirective', - 'FormControlName', - 'FormGroupDirective', - 'FormGroupName', - 'MaxLengthValidator', - 'MaxValidator', - 'MinLengthValidator', - 'MinValidator', - 'NgControlStatus', - 'NgControlStatusGroup', - 'NgForm', - 'NgModel', - 'NgModelGroup', - 'NgSelectOption', - 'NumberValueAccessor', - 'PatternValidator', - 'RadioControlValueAccessor', - 'RangeValueAccessor', - 'RequiredValidator', - 'SelectControlValueAccessor', - 'SelectMultipleControlValueAccessor', - 'RouterLink', - 'RouterLinkActive', - 'RouterLinkWithHref', - 'RouterOutlet', - 'UpgradeComponent', -]; - -const ignoredAngularInjectors = new Set([ - 'Null Injector', - ...ANGULAR_DIRECTIVES, - ...ANGULAR_DIRECTIVES.map((directive) => `_${directive}`), -]); - export function filterOutInjectorsWithNoProviders(injectorPaths: InjectorPath[]): InjectorPath[] { for (const injectorPath of injectorPaths) { injectorPath.path = injectorPath.path.filter( @@ -226,6 +250,43 @@ export function filterOutInjectorsWithNoProviders(injectorPaths: InjectorPath[]) export function filterOutAngularInjectors(injectorPaths: InjectorPath[]): InjectorPath[] { return injectorPaths.map(({node, path}) => { - return {node, path: path.filter((injector) => !ignoredAngularInjectors.has(injector.name))}; + return {node, path: path.filter((injector) => !IGNORED_ANGULAR_INJECTORS.has(injector.name))}; }); } + +export function d3InjectorTreeLinkModifier(link: SvgD3Link) { + link + .attr('hidden', (node: InjectorTreeD3Node) => { + const parentId = node.parent?.data.injector.id; + return parentId === 'N/A' ? 'true' : null; + }) + .attr('data-id', (node: InjectorTreeD3Node) => { + const from = node.data.injector.id; + const to = node.parent?.data.injector.id; + + if (from && to) { + return `${from}-to-${to}`; + } + return ''; + }); +} + +export function d3InjectorTreeNodeModifier(d3Node: SvgD3Node) { + d3Node + .attr('class', (node: InjectorTreeD3Node) => { + return [d3Node.attr('class'), INJECTOR_TYPE_CLASS_MAP.get(node.data.injector.type) ?? ''] + .filter((c) => !!c) + .join(' '); + }) + .attr('hidden', (node: InjectorTreeD3Node) => (node.data.injector.id === 'N/A' ? 'true' : null)) + .attr('data-id', (node: InjectorTreeD3Node) => { + return node.data.injector.id; + }) + .attr('data-component-id', (node: InjectorTreeD3Node) => { + if (node.data.injector.type === 'element') { + const injector = node.data.injector; + return injector.node?.component?.id ?? -1; + } + return -1; + }); +} diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree.component.ts index ac44cb1638b..3a73fe077c8 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree.component.ts @@ -31,18 +31,19 @@ import { } from '../../../../../protocol'; import {SplitAreaDirective, SplitComponent} from '../../vendor/angular-split/public_api'; -import { - InjectorTreeD3Node, - InjectorTreeVisualizer, -} from '../dependency-injection/injector-tree-visualizer'; -import {TreeVisualizerHostComponent} from '../tree-visualizer-host/tree-visualizer-host.component'; +import {TreeVisualizer} from '../../shared/tree-visualizer-host/tree-visualizer'; +import {TreeVisualizerHostComponent} from '../../shared/tree-visualizer-host/tree-visualizer-host.component'; import {InjectorProvidersComponent} from './injector-providers/injector-providers.component'; import { + d3InjectorTreeLinkModifier, + d3InjectorTreeNodeModifier, filterOutAngularInjectors, filterOutInjectorsWithNoProviders, generateEdgeIdsFromNodeIds, getInjectorIdsToRootFromNode, grabInjectorPathsFromDirectiveForest, + InjectorTreeD3Node, + InjectorTreeVisualizer, splitInjectorPathsIntoElementAndEnvironmentPaths, transformInjectorResolutionPathsIntoTree, } from './injector-tree-fns'; @@ -305,7 +306,10 @@ export class InjectorTreeComponent { const g = environmentTree.group().nativeElement; this.injectorTreeGraph?.cleanup?.(); - this.injectorTreeGraph = new InjectorTreeVisualizer(svg, g); + this.injectorTreeGraph = new TreeVisualizer(svg, g, { + d3NodeModifier: d3InjectorTreeNodeModifier, + d3LinkModifier: d3InjectorTreeLinkModifier, + }); } setUpElementInjectorVisualizer(): void { @@ -318,7 +322,11 @@ export class InjectorTreeComponent { const g = elementTree.group().nativeElement; this.elementInjectorTreeGraph?.cleanup?.(); - this.elementInjectorTreeGraph = new InjectorTreeVisualizer(svg, g, {nodeSeparation: () => 1}); + this.elementInjectorTreeGraph = new TreeVisualizer(svg, g, { + nodeSeparation: () => 1, + d3NodeModifier: d3InjectorTreeNodeModifier, + d3LinkModifier: d3InjectorTreeLinkModifier, + }); } highlightPathFromSelectedInjector(): void { diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/BUILD.bazel index a19dc2b6920..64cf80457ae 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/BUILD.bazel @@ -22,7 +22,7 @@ ng_project( srcs = [ "route-details-row.component.ts", "router-tree.component.ts", - "router-tree-visualizer.ts", + "router-tree-fns.ts", ], angular_assets = [ ":router-tree.component.html", @@ -41,7 +41,7 @@ ng_project( "//:node_modules/rxjs", "//devtools/projects/ng-devtools/src/lib/application-operations:application-operations_rjs", "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager_rjs", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/tree-visualizer-host:tree-visualizer-host_rjs", + "//devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host:tree-visualizer-host_rjs", "//devtools/projects/ng-devtools/src/lib/vendor/angular-split:angular-split_rjs", "//devtools/projects/protocol:protocol_rjs", ], @@ -52,6 +52,7 @@ ts_test_library( srcs = [ "route-details-row.component.spec.ts", "router-tree.component.spec.ts", + "router-tree-fns.spec.ts", ], interop_deps = [ ":router-tree", diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-fns.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-fns.spec.ts new file mode 100644 index 00000000000..22397cd6a1a --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-fns.spec.ts @@ -0,0 +1,100 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Route} from '../../../../../protocol'; +import { + getRouteLabel, + mapRoute, + RouterTreeNode, + transformRoutesIntoVisTree, +} from './router-tree-fns'; + +describe('router-tree-fns', () => { + describe('getRouteLabel', () => { + it('should return route label', () => { + const route = { + path: '/foo/bar', + } as Route; + const parent = { + path: '/foo', + } as Route; + + expect(getRouteLabel(route, parent, false)).toEqual('/bar'); + }); + + it('should return full route label', () => { + const route = { + path: '/foo/bar', + } as Route; + const parent = { + path: '/foo', + } as Route; + + expect(getRouteLabel(route, parent, true)).toEqual('/foo/bar'); + }); + }); + + describe('mapRoute', () => { + it('should map route', () => { + const route = { + isActive: true, + path: '/foo/bar', + } as Route; + const parent = { + path: '/foo', + } as Route; + + const treeNode = mapRoute(route, parent, false); + expect(treeNode).toEqual({ + isActive: true, + path: '/foo/bar', + label: '/bar', + children: [] as RouterTreeNode[], + } as RouterTreeNode); + }); + }); + + describe('transformRoutesIntoVisTree', () => { + it('should transform routes to visualizer tree', () => { + const rootRoute = { + path: '', + children: [ + { + path: '/home', + }, + { + path: '/list', + children: [{path: '/list/foo'}, {path: '/list/bar'}], + }, + ], + } as Route; + + const rootNode = transformRoutesIntoVisTree(rootRoute, false); + + expect(rootNode).toEqual({ + label: '', + path: '', + children: [ + { + path: '/home', + label: '/home', + children: [] as RouterTreeNode[], + }, + { + path: '/list', + label: '/list', + children: [ + {path: '/list/foo', label: '/foo', children: []}, + {path: '/list/bar', label: '/bar', children: []}, + ], + }, + ], + } as RouterTreeNode); + }); + }); +}); diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-fns.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-fns.ts new file mode 100644 index 00000000000..d4971247c11 --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-fns.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + TreeD3Node, + TreeNode, + TreeVisualizer, +} from '../../shared/tree-visualizer-host/tree-visualizer'; +import {Route} from '../../../../../protocol'; + +export interface RouterTreeNode extends TreeNode, Route { + children: RouterTreeNode[]; +} + +export type RouterTreeVisualizer = TreeVisualizer; +export type RouterTreeD3Node = TreeD3Node; + +export function getRouteLabel( + route: Route | RouterTreeNode, + parent: Route | RouterTreeNode | undefined, + showFullPath: boolean, +): string { + return (showFullPath ? route.path : route.path.replace(parent?.path || '', '')) || ''; +} + +export function mapRoute( + route: Route, + parent: Route | undefined, + showFullPath: boolean, +): RouterTreeNode { + return { + ...route, + label: getRouteLabel(route, parent, showFullPath), + children: [], + }; +} + +export function transformRoutesIntoVisTree(root: Route, showFullPath: boolean): RouterTreeNode { + let rootNode: RouterTreeNode | undefined; + const routesQueue: {route: Route; parent?: RouterTreeNode}[] = [{route: root}]; + + while (routesQueue.length) { + const {route, parent} = routesQueue.shift()!; + const routeNode = mapRoute(route, parent, showFullPath); + + if (!rootNode) { + rootNode = routeNode; + } + + if (parent) { + parent.children.push(routeNode); + } + + if (route.children) { + for (const child of route.children) { + routesQueue.push({route: child, parent: routeNode}); + } + } + } + + return rootNode!; +} diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-visualizer.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-visualizer.ts deleted file mode 100644 index a842cb82c71..00000000000 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree-visualizer.ts +++ /dev/null @@ -1,305 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import * as d3 from 'd3'; -import {Route} from '../../../../../protocol'; - -let arrowDefId = 0; - -export type RouterTreeD3Node = d3.HierarchyPointNode; - -interface RouterTreeVisualizerConfig { - orientation: 'horizontal' | 'vertical'; - nodeSize: [width: number, height: number]; - nodeSeparation: (nodeA: RouterTreeD3Node, nodeB: RouterTreeD3Node) => number; - nodeLabelSize: [width: number, height: number]; -} - -export class RouterTreeVisualizer { - private readonly config: RouterTreeVisualizerConfig; - private d3 = d3; - private root: RouterTreeD3Node | null = null; - private zoomController: d3.ZoomBehavior | null = null; - - protected nodeClickListeners: ((pointerEvent: PointerEvent, node: RouterTreeD3Node) => void)[] = - []; - protected nodeMouseoverListeners: (( - pointerEvent: PointerEvent, - node: RouterTreeD3Node, - ) => void)[] = []; - protected nodeMouseoutListeners: (( - pointerEvent: PointerEvent, - node: RouterTreeD3Node, - ) => void)[] = []; - - constructor( - private _containerElement: HTMLElement, - private _graphElement: HTMLElement, - { - orientation = 'horizontal', - nodeSize = [200, 500], - nodeSeparation = () => 2.5, - nodeLabelSize = [300, 60], - }: Partial = {}, - ) { - this.config = { - orientation, - nodeSize, - nodeSeparation, - nodeLabelSize, - }; - } - - onNodeClick(cb: (pointerEvent: PointerEvent, node: RouterTreeD3Node) => void): void { - this.nodeClickListeners.push(cb); - } - - onNodeMouseover(cb: (pointerEvent: PointerEvent, node: RouterTreeD3Node) => void): void { - this.nodeMouseoverListeners.push(cb); - } - - onNodeMouseout(cb: (pointerEvent: PointerEvent, node: RouterTreeD3Node) => void): void { - this.nodeMouseoutListeners.push(cb); - } - - private zoomScale(scale: number) { - if (this.zoomController) { - this.zoomController.scaleTo( - this.d3.select(this._containerElement), - scale, - ); - } - } - - snapToRoot(scale = 1): void { - if (this.root) { - this.snapToNode(this.root, scale); - } - } - - snapToNode(node: RouterTreeD3Node, scale = 1): void { - const svg = this.d3.select(this._containerElement); - const halfHeight = this._containerElement.clientHeight / 2; - const t = d3.zoomIdentity.translate(250, halfHeight - node.x).scale(scale); - svg.transition().duration(500).call(this.zoomController!.transform, t); - } - - get graphElement(): HTMLElement { - return this._graphElement; - } - - private getNodeById(id: string): RouterTreeD3Node | null { - const selection = this.d3 - .select(this._containerElement) - .select(`.node[data-id="${id}"]`); - if (selection.empty()) { - return null; - } - return selection.datum(); - } - - cleanup(): void { - this.d3.select(this._graphElement).selectAll('*').remove(); - this.nodeClickListeners = []; - this.nodeMouseoverListeners = []; - this.nodeMouseoutListeners = []; - } - - render(route: Route, filterRegex: RegExp, showFullPath: boolean): void { - // cleanup old graph - this.cleanup(); - - const data = this.d3.hierarchy(route, (node: Route) => node.children); - const tree = this.d3.tree(); - const svg = this.d3.select(this._containerElement); - const g = this.d3.select(this._graphElement); - - const size = 20; - - svg.selectAll('text').remove(); - svg.selectAll('rect').remove(); - svg.selectAll('defs').remove(); - - svg - .append('rect') - .attr('x', 10) - .attr('y', 10) - .attr('width', size) - .attr('height', size) - .style('stroke', 'var(--red-05)') - .style('fill', 'var(--red-06)'); - - svg - .append('rect') - .attr('x', 10) - .attr('y', 45) - .attr('width', size) - .attr('height', size) - .style('stroke', 'var(--blue-02)') - .style('fill', 'var(--blue-03)'); - - svg - .append('rect') - .attr('x', 10) - .attr('y', 80) - .attr('width', size) - .attr('height', size) - .style('stroke', 'var(--green-02)') - .style('fill', 'var(--green-03)'); - - svg - .append('text') - .attr('x', 37) - .attr('y', 21) - .attr('class', 'legend-router-tree') - .text('Eager loaded routes') - .attr('alignment-baseline', 'middle'); - - svg - .append('text') - .attr('x', 37) - .attr('y', 56) - .attr('class', 'legend-router-tree') - .text('Lazy Loaded Route') - .attr('alignment-baseline', 'middle'); - - svg - .append('text') - .attr('x', 37) - .attr('y', 92) - .attr('class', 'legend-router-tree') - .text('Active Route') - .attr('alignment-baseline', 'middle'); - - this.zoomController = this.d3.zoom().scaleExtent([0.1, 2]); - this.zoomController.on('start zoom end', (e: {transform: number}) => { - g.attr('transform', e.transform); - }); - svg.call(this.zoomController); - - // Compute the new tree layout. - tree.nodeSize(this.config.nodeSize); - tree.separation((a: RouterTreeD3Node, b: RouterTreeD3Node) => { - return this.config.nodeSeparation(a, b); - }); - - const nodes = tree(data); - this.root = nodes; - - arrowDefId++; - svg - .append('svg:defs') - .selectAll('marker') - .data([`end${arrowDefId}`]) // Different link/path types can be defined here - .enter() - .append('svg:marker') // This section adds in the arrows - .attr('id', String) - .attr('viewBox', '0 -5 10 10') - .attr('refX', 15) - .attr('refY', 0) - .attr('class', 'arrow') - .attr('markerWidth', 6) - .attr('markerHeight', 6) - .attr('orient', 'auto') - .append('svg:path') - .attr('d', 'M0,-5L10,0L0,5'); - - g.selectAll('.link') - .data(nodes.descendants().slice(1)) - .enter() - .append('path') - .attr('class', (node: RouterTreeD3Node) => { - return `link`; - }) - .attr('d', (node: RouterTreeD3Node) => { - const parent = node.parent!; - if (this.config.orientation === 'horizontal') { - return `M${node.y},${node.x},C${(node.y + parent.y) / 2}, ${node.x} ${(node.y + parent.y) / 2},${parent.x} ${parent.y},${parent.x}`; - } - - return `M${node.x},${node.y},C${(node.x + parent.x) / 2}, ${node.y} ${(node.x + parent.x) / 2},${parent.y} ${parent.x},${parent.y}`; - }); - - // Declare the nodes - const node = g - .selectAll('g.node') - .data(nodes.descendants()) - .enter() - .append('g') - .attr('class', (node: RouterTreeD3Node) => { - return `node`; - }) - .attr('transform', (node: RouterTreeD3Node) => { - if (this.config.orientation === 'horizontal') { - return `translate(${node.y},${node.x})`; - } - return `translate(${node.x},${node.y})`; - }) - .on('click', (pointerEvent: PointerEvent, node: RouterTreeD3Node) => { - for (const listener of this.nodeClickListeners) { - listener(pointerEvent, node); - } - }) - .on('mouseover', (pointerEvent: PointerEvent, node: RouterTreeD3Node) => { - for (const listener of this.nodeMouseoverListeners) { - listener(pointerEvent, node); - } - }) - .on('mouseout', (pointerEvent: PointerEvent, node: RouterTreeD3Node) => { - for (const listener of this.nodeMouseoutListeners) { - listener(pointerEvent, node); - } - }); - const [width, height] = this.config.nodeLabelSize!; - - node - .append('foreignObject') - .attr('width', width) - .attr('height', height) - .attr('x', -1 * (width - 10)) - .attr('y', -1 * (height / 2)) - .append('xhtml:div') - .attr('title', (node: RouterTreeD3Node) => { - return node.data.path || ''; - }) - .attr('class', (node: RouterTreeD3Node) => { - const label = - (showFullPath - ? node.data.path - : node.data.path.replace(node.parent?.data.path || '', '')) || ''; - const isMatched = filterRegex.test(label.toLowerCase()); - - const nodeClasses = ['node-container']; - if (node.data.isActive) { - nodeClasses.push('node-element'); - } else if (node.data.isLazy) { - nodeClasses.push('node-lazy'); - } else { - nodeClasses.push('node-environment'); - } - - if (isMatched) { - nodeClasses.push('node-search'); - } - return nodeClasses.join(' '); - }) - .text((node: RouterTreeD3Node) => { - const label = - (showFullPath - ? node.data.path - : node.data.path.replace(node.parent?.data.path || '', '')) || ''; - const lengthLimit = 25; - const labelText = - label.length > lengthLimit ? label.slice(0, lengthLimit - '...'.length) + '...' : label; - - return labelText; - }); - - svg.attr('height', '100%').attr('width', '100%'); - } -} diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree.component.ts index a1c98b296ae..beed7611373 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree.component.ts @@ -9,9 +9,8 @@ import {CommonModule} from '@angular/common'; import {afterNextRender, Component, effect, inject, input, signal, viewChild} from '@angular/core'; import {MatInputModule} from '@angular/material/input'; -import {RouterTreeD3Node, RouterTreeVisualizer} from './router-tree-visualizer'; import {MatCheckboxModule} from '@angular/material/checkbox'; -import {TreeVisualizerHostComponent} from '../tree-visualizer-host/tree-visualizer-host.component'; +import {TreeVisualizerHostComponent} from '../../shared/tree-visualizer-host/tree-visualizer-host.component'; import {SplitAreaDirective, SplitComponent} from '../../vendor/angular-split/public_api'; import {MatIconModule} from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button'; @@ -20,6 +19,14 @@ import {RouteDetailsRowComponent} from './route-details-row.component'; import {MatTableModule} from '@angular/material/table'; import {FrameManager} from '../../application-services/frame_manager'; import {Events, MessageBus, Route} from '../../../../../protocol'; +import {SvgD3Node, TreeVisualizer} from '../../shared/tree-visualizer-host/tree-visualizer'; +import { + RouterTreeVisualizer, + RouterTreeD3Node, + transformRoutesIntoVisTree, + RouterTreeNode, + getRouteLabel, +} from './router-tree-fns'; const DEFAULT_FILTER = /.^/; @@ -88,8 +95,9 @@ export class RouterTreeComponent { const group = this.routerTree().group().nativeElement; this.routerTreeVisualizer?.cleanup?.(); - this.routerTreeVisualizer = new RouterTreeVisualizer(container, group, { + this.routerTreeVisualizer = new TreeVisualizer(container, group, { nodeSeparation: () => 1, + d3NodeModifier: (n) => this.d3NodeModifier(n), }); this.visualizerReady.set(true); @@ -103,9 +111,13 @@ export class RouterTreeComponent { } renderGraph(routes: Route[]): void { - this.routerTreeVisualizer?.render(routes[0], this.filterRegex, this.showFullPath); + const root = transformRoutesIntoVisTree(routes[0], this.showFullPath); + this.routerTreeVisualizer?.render(root); this.routerTreeVisualizer?.onNodeClick((_, node) => { this.selectedRoute.set(node); + setTimeout(() => { + this.routerTreeVisualizer?.snapToNode(node, 0.7); + }); }); } @@ -124,4 +136,25 @@ export class RouterTreeComponent { navigateRoute(route: any): void { this.messageBus.emit('navigateRoute', [route.data.path]); } + + private d3NodeModifier(d3Node: SvgD3Node) { + d3Node.attr('class', (node: RouterTreeD3Node) => { + const name = getRouteLabel(node.data, node.parent?.data, this.showFullPath); + const isMatched = this.filterRegex.test(name.toLowerCase()); + + const nodeClasses = [d3Node.attr('class')]; + if (node.data.isActive) { + nodeClasses.push('node-element'); + } else if (node.data.isLazy) { + nodeClasses.push('node-lazy'); + } else { + nodeClasses.push('node-environment'); + } + + if (isMatched) { + nodeClasses.push('node-search'); + } + return nodeClasses.join(' '); + }); + } } diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/tree-visualizer-host/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/BUILD.bazel similarity index 79% rename from devtools/projects/ng-devtools/src/lib/devtools-tabs/tree-visualizer-host/BUILD.bazel rename to devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/BUILD.bazel index 03d1b5d9633..8cf5dc21f05 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/tree-visualizer-host/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/BUILD.bazel @@ -12,6 +12,8 @@ sass_binary( ng_project( name = "tree-visualizer-host", srcs = [ + "graph-renderer.ts", + "tree-visualizer.ts", "tree-visualizer-host.component.ts", ], angular_assets = [ @@ -20,4 +22,8 @@ ng_project( interop_deps = [ "//packages/core", ], + deps = [ + "//:node_modules/@types/d3", + "//:node_modules/d3", + ], ) diff --git a/devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/graph-renderer.ts b/devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/graph-renderer.ts new file mode 100644 index 00000000000..fef54ed3b1b --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/graph-renderer.ts @@ -0,0 +1,39 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export abstract class GraphRenderer { + abstract render(graph: T): void; + abstract getNodeById(id: string): U | null; + abstract snapToNode(node: U): void; + abstract snapToRoot(): void; + abstract zoomScale(scale: number): void; + abstract root: U | null; + abstract get graphElement(): HTMLElement; + + protected nodeClickListeners: ((pointerEvent: PointerEvent, node: U) => void)[] = []; + protected nodeMouseoverListeners: ((pointerEvent: PointerEvent, node: U) => void)[] = []; + protected nodeMouseoutListeners: ((pointerEvent: PointerEvent, node: U) => void)[] = []; + + cleanup(): void { + this.nodeClickListeners = []; + this.nodeMouseoverListeners = []; + this.nodeMouseoutListeners = []; + } + + onNodeClick(cb: (pointerEvent: PointerEvent, node: U) => void): void { + this.nodeClickListeners.push(cb); + } + + onNodeMouseover(cb: (pointerEvent: PointerEvent, node: U) => void): void { + this.nodeMouseoverListeners.push(cb); + } + + onNodeMouseout(cb: (pointerEvent: PointerEvent, node: U) => void): void { + this.nodeMouseoutListeners.push(cb); + } +} diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/tree-visualizer-host/tree-visualizer-host.component.scss b/devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/tree-visualizer-host.component.scss similarity index 56% rename from devtools/projects/ng-devtools/src/lib/devtools-tabs/tree-visualizer-host/tree-visualizer-host.component.scss rename to devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/tree-visualizer-host.component.scss index e6de1de99d5..f0ab10361e8 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/tree-visualizer-host/tree-visualizer-host.component.scss +++ b/devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/tree-visualizer-host.component.scss @@ -13,11 +13,6 @@ } ::ng-deep { - .node-hidden, - .link-hidden { - display: none; - } - .legend { background: var(--primary-contrast); } @@ -42,54 +37,29 @@ .node { cursor: pointer; + border: 1px solid var(--quaternary-contrast); + background: white; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + color: black; + font-size: 16px; + box-sizing: border-box; + border-radius: 2px; + border-style: solid; + border-width: 2px; + font-weight: 300; - .node-container { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - color: black; - font-size: 16px; - box-sizing: border-box; - border-radius: 2px; - border-style: solid; - border-width: 2px; - font-weight: 300; - - &:hover { - color: white; - } - } - - &.highlighted { - .node-container, - .node-container:hover { - background: var(--blue-02); - border-color: white; - color: white; - } - - &.selected { - .node-container, - .node-container:hover { - color: var(--blue-02); - background: white; - border-width: 3px; - border-color: var(--blue-02); - font-weight: 600; - } - } - } - - .node-search { + &.node-search { border-width: 4px !important; border-style: groove !important; text-decoration: underline; font-weight: bold; } - .node-environment { + &.node-environment { border: 1px solid var(--red-05); background: var(--red-06); @@ -98,7 +68,7 @@ } } - .node-imported-module { + &.node-imported-module { border-color: var(--purple-02); background: var(--purple-03); @@ -107,7 +77,7 @@ } } - .node-lazy { + &.node-lazy { border-color: var(--blue-02); background: var(--blue-03); @@ -116,7 +86,7 @@ } } - .node-element { + &.node-element { border-color: var(--green-02); background: var(--green-03); @@ -125,7 +95,7 @@ } } - .node-null { + &.node-null { border: 1px solid var(--quaternary-contrast); background: white; @@ -134,10 +104,20 @@ } } - .node-label { - color: black; - font-size: 18px; - text-align: center; + &.highlighted, + &.highlighted:hover { + background: var(--blue-02); + border-color: white; + color: white; + } + + &.selected, + &.selected:hover { + color: var(--blue-02); + background: white; + border-width: 3px; + border-color: var(--blue-02); + font-weight: 600; } } } diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/tree-visualizer-host/tree-visualizer-host.component.ts b/devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/tree-visualizer-host.component.ts similarity index 100% rename from devtools/projects/ng-devtools/src/lib/devtools-tabs/tree-visualizer-host/tree-visualizer-host.component.ts rename to devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/tree-visualizer-host.component.ts diff --git a/devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/tree-visualizer.ts b/devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/tree-visualizer.ts new file mode 100644 index 00000000000..52a38863371 --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/shared/tree-visualizer-host/tree-visualizer.ts @@ -0,0 +1,246 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import * as d3 from 'd3'; +import {GraphRenderer} from './graph-renderer'; + +let arrowDefId = 0; + +const MAX_NODE_LABEL_LENGTH = 25; + +export interface TreeNode { + label: string; + children: TreeNode[]; +} + +export type TreeD3Node = d3.HierarchyPointNode; + +export type SvgD3Node = d3.Selection< + d3.BaseType, + TreeD3Node, + HTMLElement, + TreeD3Node +>; + +export type SvgD3Link = d3.Selection< + SVGPathElement, + d3.HierarchyPointNode, + HTMLElement, + TreeD3Node +>; + +export interface TreeVisualizerConfig { + /** WARNING: For vertically-oriented trees, use separation greater than `1` */ + orientation: 'horizontal' | 'vertical'; + nodeSize: [width: number, height: number]; + nodeSeparation: (nodeA: TreeD3Node, nodeB: TreeD3Node) => number; + nodeLabelSize: [width: number, height: number]; + /** Perform custom changes on the SVG node (e.g. set classes, colors, attributes, etc.) */ + d3NodeModifier: (node: SvgD3Node) => void; + /** Perform custom changes on the SVG link (e.g. set classes, colors, attributes, etc.) */ + d3LinkModifier: (link: SvgD3Link) => void; +} + +export class TreeVisualizer extends GraphRenderer> { + private zoomController: d3.ZoomBehavior | null = null; + private readonly config: TreeVisualizerConfig; + private readonly defaultConfig: TreeVisualizerConfig = { + orientation: 'horizontal', + nodeSize: [200, 500], + nodeSeparation: () => 2, + nodeLabelSize: [250, 60], + d3NodeModifier: () => {}, + d3LinkModifier: () => {}, + }; + + constructor( + private readonly containerElement: HTMLElement, + public readonly graphElement: HTMLElement, + config: Partial> = {}, + ) { + super(); + + this.config = { + ...this.defaultConfig, + ...config, + }; + } + + override root: TreeD3Node | null = null; + + override zoomScale(scale: number) { + if (this.zoomController) { + this.zoomController.scaleTo(d3.select(this.containerElement), scale); + } + } + + override snapToRoot(scale = 1): void { + if (this.root) { + this.snapToNode(this.root, scale); + } + } + + override snapToNode(node: TreeD3Node, scale = 1): void { + const svg = d3.select(this.containerElement); + const contHalfWidth = this.containerElement.clientWidth / 2; + const contHalfHeight = this.containerElement.clientHeight / 2; + const {x, y} = this.getNodeCoor(node); + + const t = d3.zoomIdentity + .translate(contHalfWidth, contHalfHeight) + .scale(scale) + .translate(-x, -y); + svg.transition().duration(500).call(this.zoomController!.transform, t); + } + + override getNodeById(id: string): TreeD3Node | null { + const selection = d3 + .select>(this.containerElement) + .select(`.node[data-id="${id}"]`); + if (selection.empty()) { + return null; + } + return selection.datum(); + } + + override cleanup(): void { + super.cleanup(); + d3.select(this.graphElement).selectAll('*').remove(); + } + + override render(graph: T): void { + // cleanup old graph + this.cleanup(); + + const data = d3.hierarchy(graph, (node: T) => node.children as Iterable); + const tree = d3.tree(); + const svg = d3.select(this.containerElement); + const g = d3.select>(this.graphElement); + + this.zoomController = d3.zoom().scaleExtent([0.1, 2]); + this.zoomController.on('start zoom end', (e: {transform: number}) => { + g.attr('transform', e.transform); + }); + svg.call(this.zoomController); + + // Compute the new tree layout. + tree.nodeSize(this.config.nodeSize); + tree.separation((a: TreeD3Node, b: TreeD3Node) => { + return this.config.nodeSeparation(a, b); + }); + + const nodes = tree(data); + this.root = nodes; + + arrowDefId++; + svg + .append('svg:defs') + .selectAll('marker') + .data([`end${arrowDefId}`]) // Different link/path types can be defined here + .enter() + .append('svg:marker') // This section adds in the arrows + .attr('id', String) + .attr('viewBox', '0 -5 10 10') + .attr('refX', 10) + .attr('refY', 0) + .attr('class', 'arrow') + .attr('markerWidth', 6) + .attr('markerHeight', 6) + .attr('orient', 'auto') + .append('svg:path') + .attr('d', 'M0,-5L10,0L0,5'); + + const [labelWidth, labelHeight] = this.config.nodeLabelSize; + const halfLabelWidth = labelWidth / 2; + const halfLabelHeight = labelHeight / 2; + + const d3Link = g + .selectAll('.link') + .data(nodes.descendants().slice(1)) + .enter() + .append('path') + .attr('class', 'link') + .attr('marker-end', `url(#end${arrowDefId})`) + .attr('d', (node: TreeD3Node) => { + const {x, y} = this.getNodeCoor(node); + const {x: parentX, y: parentY} = this.getNodeCoor(node.parent!); + + if (this.config.orientation === 'horizontal') { + return ` + M${x - halfLabelWidth},${y} + C${(x + parentX) / 2}, + ${y} ${(x + parentX) / 2}, + ${parentY} ${parentX + halfLabelWidth}, + ${parentY}`; + } + + return ` + M${x},${y - halfLabelHeight} + C${x}, + ${(y + parentY) / 2} ${parentX}, + ${(y + parentY) / 2} ${parentX}, + ${parentY + halfLabelHeight}`; + }); + + this.config.d3LinkModifier(d3Link); + + // Declare the nodes + const d3Node = g + .selectAll('g.node-group') + .data(nodes.descendants()) + .enter() + .append('g') + .attr('class', 'node-group') + .on('click', (pointerEvent: PointerEvent, node: TreeD3Node) => { + this.nodeClickListeners.forEach((listener) => listener(pointerEvent, node)); + }) + .on('mouseover', (pointerEvent: PointerEvent, node: TreeD3Node) => { + this.nodeMouseoverListeners.forEach((listener) => listener(pointerEvent, node)); + }) + .on('mouseout', (pointerEvent: PointerEvent, node: TreeD3Node) => { + this.nodeMouseoutListeners.forEach((listener) => listener(pointerEvent, node)); + }) + .attr('transform', (node: TreeD3Node) => { + const {x, y} = this.getNodeCoor(node); + return `translate(${x},${y})`; + }) + .append('foreignObject') + .attr('width', labelWidth) + .attr('height', labelHeight) + .attr('x', -halfLabelWidth) + .attr('y', -halfLabelHeight) + .append('xhtml:div') + .attr('class', 'node') + .attr('title', (node: TreeD3Node) => { + return node.data.label; + }) + .html((node: TreeD3Node) => { + const label = node.data.label; + return label.length > MAX_NODE_LABEL_LENGTH + ? label.slice(0, MAX_NODE_LABEL_LENGTH - '...'.length) + '...' + : label; + }); + + this.config.d3NodeModifier(d3Node); + + svg.attr('height', '100%').attr('width', '100%'); + } + + /** Returns the node coordinates based on orientation. */ + private getNodeCoor(node: TreeD3Node): {x: number; y: number} { + const {x, y} = node; + + if (this.config.orientation === 'horizontal') { + return { + x: y, + y: x, + }; + } + return {x, y}; + } +}