Skip to content

Commit 941c774

Browse files
authored
Merge pull request #63 from CoderAllan/dependencyInjectionGraph
Dependency injection graph
2 parents 138750d + b7f3816 commit 941c774

18 files changed

+291
-63
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log
22

3+
## Version 1.6.0
4+
5+
- Added Dependency injection graph command that visualizes what services are injected into the components. Graph also include all the inputs, outputs, viewchild, viewchildren, contentchild and contentchildren of each component.
6+
37
## Version 1.5.1
48

59
- Now using webpack to reduce size of extension.

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Find it on the [Visual Studio Code marketplace](https://marketplace.visualstudio
1111
## Features
1212

1313
* Show the module hierarchy
14+
* Show the dependency injection graph
1415
* Show the component hierarchy
1516
* Generate DGML graph of project component hierarchy
1617
* Summarizes all the Angular modules
@@ -34,6 +35,14 @@ In the visualization the classes will be colored depending on the class decorato
3435

3536
![Show module hierarchy](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/ShowModuleHierarchy.gif)
3637

38+
### Show the dependency injection graph [#](#show-dependency-injection-graph- 'Show the dependency injection graph')
39+
40+
The 'Show a graph representing the components and the injected dependencies' command generates a directed graph representing the components and the services injected into the components of an Angular application. The command scans all *.ts files of the application and for each class decorated with the @Component decorator, it analyses the constructor and each field in the class to identify all injected classes and to identify all the fields decorated with the Input, Output, ViewChild, ViewChildren, ContentChild and ContentChildren decorators.
41+
42+
In the visualization the components will by default be colored dark blue and the injected classes will be colored light blue.
43+
44+
![Show dependency injection graph](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/ShowDependencyInjectionGraph.gif)
45+
3746
### Show the component hierarchy [#](#show-component-hierarchy- 'Show the component hierarchy')
3847

3948
The 'Show the component hierarchy' command is used for visualizing the component hierarchy and Angular application. It analyses all the *.component.ts files and all the corresponding template files to determine how the component use each other and then generates a directed graph showing the component hierarchy.
3.39 MB
Loading

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
"displayName": "AngularTools",
55
"description": "AngularTools is a collection of tools for exploring an Angular project, help you with documenting, reverse engineering a project or help when refactoring.",
66
"icon": "logo.png",
7-
"version": "1.5.1",
7+
"version": "1.6.0",
88
"license": "MIT",
99
"repository": "https://github.com/CoderAllan/vscode-angulartools",
1010
"author": {
1111
"name": "Allan Simonsen",
1212
"url": "https://github.com/CoderAllan"
1313
},
1414
"engines": {
15-
"vscode": "^1.53.0"
15+
"vscode": "^1.54.0"
1616
},
1717
"categories": [
1818
"Other",
@@ -33,7 +33,8 @@
3333
"onCommand:angulartools.packageJsonToMarkdown",
3434
"onCommand:angulartools.showComponentHierarchy",
3535
"onCommand:angulartools.showModuleHierarchy",
36-
"onCommand:angulartools.componentHierarchyMarkdown"
36+
"onCommand:angulartools.componentHierarchyMarkdown",
37+
"onCommand:angulartools.generateDependencyInjectionGraph"
3738
],
3839
"main": "./dist/extension.js",
3940
"contributes": {
@@ -69,6 +70,10 @@
6970
{
7071
"command": "angulartools.componentHierarchyMarkdown",
7172
"title": "AngularTools: Generate a directed graph in Mermaid Markdown format."
73+
},
74+
{
75+
"command": "angulartools.generateDependencyInjectionGraph",
76+
"title": "AngularTools: Show a graph representing the components and the injected dependencies."
7277
}
7378
],
7479
"configuration": {
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { Node, Edge, ShowHierarchyBase, NodeType, ArrowType } from './showHierarchyBase';
2+
import { ModuleManager } from '@src';
3+
import { Component, Project } from '@model';
4+
import * as fs from 'fs';
5+
import * as path from 'path';
6+
import * as vscode from 'vscode';
7+
8+
export class GenerateDependencyInjectionGraph extends ShowHierarchyBase {
9+
static get commandName() { return 'generateDependencyInjectionGraph'; }
10+
public execute(webview: vscode.Webview) {
11+
this.checkForOpenWorkspace();
12+
webview.onDidReceiveMessage(
13+
message => {
14+
switch (message.command) {
15+
case 'saveAsPng':
16+
this.saveAsPng('DependencyInjectionGraph.png', message.text);
17+
return;
18+
}
19+
},
20+
undefined,
21+
this.extensionContext.subscriptions
22+
);
23+
var workspaceFolder = this.fsUtils.getWorkspaceFolder();
24+
const errors: string[] = [];
25+
const project: Project = ModuleManager.scanProject(workspaceFolder, errors, this.isTypescriptFile);
26+
27+
this.nodes = [];
28+
this.edges = [];
29+
this.addNodesAndEdges(project, this.appendNodes, this.appendEdges);
30+
const nodesJson = this.nodes
31+
.map((node, index, arr) => { return node.toJsonString(); })
32+
.join(',\n');
33+
const edgesJson = this.edges
34+
.map((edge, index, arr) => { return edge.toJsonString(); })
35+
.join(',\n');
36+
37+
try {
38+
const jsContent = this.generateJavascriptContent(nodesJson, edgesJson);
39+
const outputJsFilename = this.showModuleHierarchyJsFilename;
40+
let htmlContent = this.generateHtmlContent(webview, this.showModuleHierarchyJsFilename);
41+
//this.fsUtils.writeFile(this.extensionContext?.asAbsolutePath(path.join('out', ShowComponentHierarchy.Name + '.html')), htmlContent, () => { }); // For debugging
42+
this.fsUtils.writeFile(
43+
this.extensionContext?.asAbsolutePath(path.join('.', outputJsFilename)),
44+
jsContent,
45+
() => {
46+
webview.html = htmlContent;
47+
}
48+
);
49+
}
50+
catch (ex) {
51+
console.log('Angular Tools Exception:' + ex);
52+
}
53+
if (errors.length > 0) {
54+
this.showErrors(errors, `Parsing of ${errors.length > 1 ? 'some' : 'one'} of the project files failed.\n`);
55+
}
56+
}
57+
58+
generatedComponentNode(component: Component): string {
59+
let nodeContent: string = '';
60+
nodeContent = `<b>${component.name}</b>`;
61+
if(component.inputs.length > 0) {
62+
const inputs = component.inputs.join(", ");
63+
nodeContent += `\\n<b>Inputs:</b> ${inputs}`;
64+
}
65+
if(component.outputs.length > 0) {
66+
const outputs = component.outputs.join(", ");
67+
nodeContent += `\\n<b>Outputs:</b> ${outputs}`;
68+
}
69+
if(component.viewchilds.length > 0) {
70+
const viewchilds = component.viewchilds.join(", ");
71+
nodeContent += `\\n<b>Viewchilds:</b> ${viewchilds}`;
72+
}
73+
if(component.viewchildren.length > 0) {
74+
const viewchildren = component.viewchildren.join(", ");
75+
nodeContent += `\\n<b>Viewchildren:</b> ${viewchildren}`;
76+
}
77+
if(component.contentchilds.length > 0) {
78+
const contentchilds = component.contentchilds.join(", ");
79+
nodeContent += `\\n<b>Contentchilds:</b> ${contentchilds}`;
80+
}
81+
if(component.contentchildren.length > 0) {
82+
const contentchildren = component.contentchildren.join(", ");
83+
nodeContent += `\\n<b>Contentchildren:</b> ${contentchildren}`;
84+
}
85+
return nodeContent;
86+
}
87+
88+
addNodesAndEdges(project: Project, appendNodes: (nodeList: Node[]) => void, appendEdges: (edgeList: Edge[]) => void) {
89+
project.components.forEach(component => {
90+
appendNodes([new Node(component.name, this.generatedComponentNode(component), false, NodeType.component)]);
91+
component.dependencyInjections.forEach(injectable => {
92+
appendNodes([new Node(injectable, injectable, false, NodeType.injectable)]);
93+
appendEdges([new Edge((this.edges.length + 1).toString(), injectable, component.name, ArrowType.injectable)]);
94+
});
95+
});
96+
}
97+
98+
generateJavascriptContent(nodesJson: string, edgesJson: string) {
99+
let template = fs.readFileSync(this.extensionContext?.asAbsolutePath(path.join('templates', this.templateJsFilename)), 'utf8');
100+
let jsContent = template.replace('var nodes = new vis.DataSet([]);', `var nodes = new vis.DataSet([${nodesJson}]);`);
101+
jsContent = jsContent.replace('var edges = new vis.DataSet([]);', `var edges = new vis.DataSet([${edgesJson}]);`);
102+
jsContent = jsContent.replace('type: "triangle" // edge arrow to type', `type: "${this.config.visEdgeArrowToType}" // edge arrow to type}`);
103+
jsContent = jsContent.replace('ctx.strokeStyle = \'blue\'; // graph selection guideline color', `ctx.strokeStyle = '${this.config.graphSelectionGuidelineColor}'; // graph selection guideline color`);
104+
jsContent = jsContent.replace('ctx.lineWidth = 1; // graph selection guideline width', `ctx.lineWidth = ${this.config.graphSelectionGuidelineWidth}; // graph selection guideline width`);
105+
jsContent = jsContent.replace('selectionCanvasContext.strokeStyle = \'red\';', `selectionCanvasContext.strokeStyle = '${this.config.graphSelectionColor}';`);
106+
jsContent = jsContent.replace('selectionCanvasContext.lineWidth = 2;', `selectionCanvasContext.lineWidth = ${this.config.graphSelectionWidth};`);
107+
return jsContent;
108+
}
109+
}

src/commands/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export * from './projectDirectoryStructure';
77
export * from './showComponentHierarchy';
88
export * from './showModuleHierarchy';
99
export * from './componentHierarchyMarkdown';
10+
export * from './generateDependencyInjectionGraph';

src/commands/modulesToMarkdown.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import * as vscode from 'vscode';
22
import * as path from 'path';
3-
import { ArrayUtils, Config, FileSystemUtils, NgModule, Project } from '@src';
3+
import { ArrayUtils, Config, FileSystemUtils, ModuleManager, NgModule } from '@src';
44
import { CommandBase } from '@commands';
5-
import { ModuleManager } from '@src';
5+
import { Project } from '@model';
66

77
export class ModulesToMarkdown extends CommandBase {
88
private config = new Config();

src/commands/showHierarchyBase.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ import { Base64 } from 'js-base64';
44
import * as fs from 'fs';
55
import * as path from 'path';
66
import * as vscode from 'vscode';
7+
import { Project } from '@model';
78

89
export enum NodeType {
910
none,
10-
rootNode,
1111
component,
12+
directive,
13+
injectable,
1214
module,
1315
pipe,
14-
directive
16+
rootNode
1517
}
1618
export class Node {
1719
private config: Config = new Config();
@@ -48,15 +50,36 @@ export class Node {
4850
nodeColorAttr = '';
4951
break;
5052
}
51-
const label = this.name.length > this.config.maximumNodeLabelLength ? this.name.substr(0, this.config.maximumNodeLabelLength) + '...' : this.name;
53+
const label = this.config.maximumNodeLabelLength !== -1 && this.name.length > this.config.maximumNodeLabelLength ? this.name.substr(0, this.config.maximumNodeLabelLength) + '...' : this.name;
5254
return `{id: "${this.id}", label: "${label}" ${nodeColorAttr}}`;
5355
}
56+
57+
public static getNodeType(project: Project, className: string) {
58+
let nodeType = NodeType.none;
59+
if (project.moduleNames.has(className) || className.endsWith('Module') || className.includes("Module.")) {
60+
nodeType = NodeType.module;
61+
}
62+
else if (project.components.has(className) || className.endsWith('Component')) {
63+
nodeType = NodeType.component;
64+
}
65+
else if (project.directives.has(className) || className.endsWith('Directive')) {
66+
nodeType = NodeType.directive;
67+
}
68+
else if (project.pipes.has(className) || className.endsWith('Pipe')) {
69+
nodeType = NodeType.pipe;
70+
}
71+
else if (project.injectables.has(className)) {
72+
nodeType = NodeType.injectable;
73+
}
74+
return nodeType;
75+
}
5476
}
5577

5678
export enum ArrowType {
5779
none = 0,
5880
import = 1,
59-
export = 2
81+
export = 2,
82+
injectable = 3
6083
}
6184

6285
export class Edge {
@@ -164,4 +187,13 @@ export class ShowHierarchyBase extends CommandBase {
164187
htmlContent = htmlContent.replace('showHierarchy.js', jsUri.toString());
165188
return htmlContent;
166189
}
190+
191+
protected showErrors(errors: string[], errorMessage: string) {
192+
const angularToolsOutput = vscode.window.createOutputChannel(this.config.angularToolsOutputChannel);
193+
angularToolsOutput.clear();
194+
angularToolsOutput.appendLine(errorMessage);
195+
angularToolsOutput.appendLine('Below is a list of the errors.');
196+
angularToolsOutput.appendLine(errors.join('\n'));
197+
angularToolsOutput.show();
198+
}
167199
}

src/commands/showModuleHierarchy.ts

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Node, Edge, ShowHierarchyBase, NodeType, ArrowType } from './showHierarchyBase';
2-
import { ModuleManager, Project } from '@src';
2+
import { ModuleManager } from '@src';
3+
import { Project } from '@model';
34
import * as fs from 'fs';
45
import * as path from 'path';
56
import * as vscode from 'vscode';
@@ -50,40 +51,24 @@ export class ShowModuleHierarchy extends ShowHierarchyBase {
5051
console.log('Angular Tools Exception:' + ex);
5152
}
5253
if (errors.length > 0) {
53-
this.showErrors(errors);
54+
this.showErrors(errors, `Parsing of ${errors.length > 1 ? 'some' : 'one'} of the modules failed.\n`);
5455
}
5556
}
5657
addNodesAndEdges(project: Project, appendNodes: (nodeList: Node[]) => void, appendEdges: (edgeList: Edge[]) => void) {
5758
project.modules.forEach(module => {
5859
appendNodes([new Node(module.moduleName, module.moduleName, false, NodeType.module)]);
5960
module.imports.forEach(_import => {
60-
const nodeType = this.getNodeType(project, _import);
61+
const nodeType = Node.getNodeType(project, _import);
6162
appendNodes([new Node(_import, _import, false, nodeType)]);
6263
appendEdges([new Edge((this.edges.length + 1).toString(), _import, module.moduleName, ArrowType.import)]);
6364
});
6465
module.exports.forEach(_export => {
65-
const nodeType = this.getNodeType(project, _export);
66+
const nodeType = Node.getNodeType(project, _export);
6667
appendNodes([new Node(_export, _export, false, nodeType)]);
6768
appendEdges([new Edge((this.edges.length + 1).toString(), module.moduleName, _export, ArrowType.export)]);
6869
});
6970
});
7071
}
71-
getNodeType(project: Project, className: string) {
72-
let nodeType = NodeType.none;
73-
if (project.moduleNames.has(className) || className.endsWith('Module') || className.includes("Module.")) {
74-
nodeType = NodeType.module;
75-
}
76-
else if (project.components.has(className) || className.endsWith('Component')) {
77-
nodeType = NodeType.component;
78-
}
79-
else if (project.directives.has(className) || className.endsWith('Directive')) {
80-
nodeType = NodeType.directive;
81-
}
82-
else if (project.pipes.has(className) || className.endsWith('Pipe')) {
83-
nodeType = NodeType.pipe;
84-
}
85-
return nodeType;
86-
}
8772
generateJavascriptContent(nodesJson: string, edgesJson: string) {
8873
let template = fs.readFileSync(this.extensionContext?.asAbsolutePath(path.join('templates', this.templateJsFilename)), 'utf8');
8974
let jsContent = template.replace('var nodes = new vis.DataSet([]);', `var nodes = new vis.DataSet([${nodesJson}]);`);
@@ -95,13 +80,4 @@ export class ShowModuleHierarchy extends ShowHierarchyBase {
9580
jsContent = jsContent.replace('selectionCanvasContext.lineWidth = 2;', `selectionCanvasContext.lineWidth = ${this.config.graphSelectionWidth};`);
9681
return jsContent;
9782
}
98-
99-
showErrors(errors: string[]) {
100-
const angularToolsOutput = vscode.window.createOutputChannel(this.config.angularToolsOutputChannel);
101-
angularToolsOutput.clear();
102-
angularToolsOutput.appendLine(`Parsing of ${errors.length > 1 ? 'some' : 'one'} of the modules failed.\n`);
103-
angularToolsOutput.appendLine('Below is a list of the errors.');
104-
angularToolsOutput.appendLine(errors.join('\n'));
105-
angularToolsOutput.show();
106-
}
10783
}

src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class Config {
5656
public get directiveNodeShape(): string { return this.getSetting<string>('showModuleHierarchy.directiveNodeShape', 'box'); }
5757
public get importEdgeColor(): string { return this.getSetting<string>('showModuleHierarchy.importEdgeColor', '#43a047'); }
5858
public get exportEdgeColor(): string { return this.getSetting<string>('showModuleHierarchy.exportEdgeColor', '#0288d1'); }
59-
public get maximumNodeLabelLength(): number { return this.getSetting<number>('showModuleHierarchy.maximumNodeLabelLength', 40); }
59+
public get maximumNodeLabelLength(): number { return this.getSetting<number>('showModuleHierarchy.maximumNodeLabelLength', -1); }
6060

6161
// ComponentHierarchyMarkdown
6262
public get componentHierarchyMarkdownFilename(): string { return this.getSetting<string>('componentHierarchyMarkdownFilename', 'ComponentHierarchy.md'); }

0 commit comments

Comments
 (0)