Angular Component Dependency Graph Generator
Building large Angular applications often involves complex component interactions. Understanding these dependencies can be crucial for refactoring, debugging, and optimizing build processes. This challenge asks you to create a function that analyzes an Angular module and generates a dependency graph representing the relationships between its components.
Problem Description
You are tasked with creating a TypeScript function createComponentDependencyGraph that takes an Angular ModuleMetadata object as input and returns a dependency graph represented as an adjacency list. The adjacency list should be a dictionary (object) where keys are component selectors (strings) and values are arrays of component selectors that the key component depends on. A component depends on another if it imports it directly or uses its directive/pipe/component in its template.
Key Requirements:
- Input: An
AngularModuleMetadataobject. This object contains information about the module, including its declarations, imports, and providers. - Output: An adjacency list (object) representing the dependency graph.
- Dependency Detection: The function must identify dependencies based on:
- Direct Imports: Components declared in imported modules.
- Template Usage: Components, directives, or pipes used within the template of a component. Assume that template strings are provided as part of the
AngularModuleMetadata.
- Component Selectors: Use the component's selector (e.g., 'app-header') as the key in the adjacency list.
- Circular Dependencies: The function should handle circular dependencies gracefully (e.g., by not getting stuck in an infinite loop). Circular dependencies should be represented in the graph.
- Error Handling: If the input is invalid (e.g., not an AngularModuleMetadata object), the function should return an empty object.
Expected Behavior:
The function should traverse the module's declarations, imports, and templates to identify dependencies. It should then construct the adjacency list, where each key represents a component and its value is an array of components it depends on.
Edge Cases to Consider:
- Modules with no dependencies.
- Modules with circular dependencies.
- Components that import other components but don't use them in their templates.
- Components that are used in templates but not declared in the module. (Assume these are external dependencies and should be included in the graph with a selector like 'external-component-name').
- Empty templates.
- Invalid input types.
Examples
Example 1:
Input:
{
declarations: [
{ selector: 'app-header' },
{ selector: 'app-footer' },
{ selector: 'app-content', template: '<app-header></app-header><app-footer></app-footer>' }
],
imports: [],
providers: []
}
Output:
{
'app-header': ['app-header'],
'app-footer': ['app-footer'],
'app-content': ['app-header', 'app-footer']
}
Explanation: 'app-content' depends on 'app-header' and 'app-footer' because they are used in its template. 'app-header' and 'app-footer' depend on themselves as they are used in their own templates (self-dependency).
Example 2:
Input:
{
declarations: [
{ selector: 'app-header' },
{ selector: 'app-footer' },
{ selector: 'app-content', template: '<app-header></app-header>' }
],
imports: [{ loadChildren: './header/header.module.ts', path: 'header' }],
providers: []
}
Output:
{
'app-header': ['app-header'],
'app-footer': [],
'app-content': ['app-header']
}
Explanation: 'app-content' depends on 'app-header'. The imported module is not analyzed in this simplified example.
Example 3: (Edge Case - Circular Dependency)
Input:
{
declarations: [
{ selector: 'app-a', template: '<app-b></app-b>' },
{ selector: 'app-b', template: '<app-a></app-a>' }
],
imports: [],
providers: []
}
Output:
{
'app-a': ['app-b'],
'app-b': ['app-a']
}
Explanation: 'app-a' depends on 'app-b', and 'app-b' depends on 'app-a', creating a circular dependency.
Constraints
- Input Type: The input must be an object conforming to the
AngularModuleMetadatainterface (simplified for this challenge - see description above). - Time Complexity: The function should ideally have a time complexity of O(n*m), where n is the number of components and m is the average length of a component's template.
- Memory Complexity: The function should use a reasonable amount of memory, avoiding excessive data structures.
- No External Libraries: You are not allowed to use external libraries for dependency parsing.
Notes
- This challenge focuses on the core logic of dependency graph generation. You don't need to implement a full Angular module parser.
- Consider using Depth-First Search (DFS) or Breadth-First Search (BFS) to traverse the module and identify dependencies.
- The
templateproperty in theAngularModuleMetadatais a simplified representation of the component's template. You'll need to parse this string to identify dependencies. A simple regex might be sufficient for this challenge. - Focus on clarity and correctness. Well-structured and commented code is highly valued.
- Assume that component selectors are unique within the module.
- The
loadChildrenproperty in theimportsarray represents lazy-loaded modules. For simplicity, you can ignore these modules in this challenge.