Hone logo
Hone
Problems

Angular Dependency Tracker

This challenge requires you to build a system within an Angular application that can track dependencies between different components or services. This is crucial for understanding the architecture of larger applications, identifying potential circular dependencies, and optimizing code organization.

Problem Description

Your task is to create a mechanism in Angular that allows components and services to declare their dependencies and for a central tracker to log these declarations. The system should be able to:

  1. Declare Dependencies: Provide a way for Angular components and services to register their dependencies on other components or services.
  2. Track Dependencies: Maintain a record of all declared dependencies, mapping each "dependent" entity to its list of "dependencies."
  3. Report Dependencies: Offer a method to retrieve the dependency tree for a specific component or service, or a global view of all tracked dependencies.
  4. Identify Circular Dependencies (Optional but Recommended): Implement logic to detect if a dependency cycle exists within the tracked graph.

Key Requirements:

  • Use Angular's dependency injection system to facilitate the registration of dependencies.
  • The tracking mechanism should be a singleton service.
  • The output should be easily consumable for analysis.

Expected Behavior:

When a component or service is initialized or constructed, it should register its dependencies with the tracker. The tracker should then store this information. A separate method should allow querying this stored information.

Edge Cases:

  • Dependencies on non-existent entities (e.g., declaring a dependency on a service that isn't provided).
  • Components/services with no dependencies.
  • Circular dependencies (if the optional requirement is implemented).

Examples

Example 1: Simple Component Dependency

// app.module.ts
@NgModule({
  declarations: [
    AppComponent,
    ComponentAComponent,
    ComponentBComponent
  ],
  imports: [BrowserModule],
  providers: [
    DependencyTrackerService, // Provide the tracker
    ServiceXService          // A service that ComponentA depends on
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

// service-x.service.ts
@Injectable({ providedIn: 'root' })
export class ServiceXService {}

// component-a.component.ts
@Component({ /* ... */ })
export class ComponentAComponent {
  constructor(
    private dependencyTracker: DependencyTrackerService,
    private serviceX: ServiceXService
  ) {
    this.dependencyTracker.registerDependency('ComponentA', 'ServiceX');
  }
}

// component-b.component.ts
@Component({ /* ... */ })
export class ComponentBComponent {
  constructor(
    private dependencyTracker: DependencyTrackerService
  ) {
    this.dependencyTracker.registerDependency('ComponentB', 'ComponentA');
  }
}

// In some part of your app (e.g., AppComponent's template or a debug component)
// Assuming 'tracker' is an instance of DependencyTrackerService
// tracker.getDependencies('ComponentA')

// Expected Output from tracker.getDependencies('ComponentA'):
// {
//   dependent: 'ComponentA',
//   dependencies: ['ServiceX']
// }

// Expected Output from tracker.getAllDependencies():
// {
//   'ComponentA': ['ServiceX'],
//   'ComponentB': ['ComponentA']
// }

Explanation: ComponentA declares a dependency on ServiceX. ComponentB declares a dependency on ComponentA. The DependencyTrackerService logs these relationships.

Example 2: No Dependencies

// component-c.component.ts
@Component({ /* ... */ })
export class ComponentCComponent {
  constructor(
    private dependencyTracker: DependencyTrackerService
  ) {
    // No explicit registration needed if the component has no dependencies
    // or if the tracker can infer from constructor arguments (more advanced)
    // For this example, we assume explicit registration for simplicity
  }
}

// In some part of your app
// tracker.getDependencies('ComponentC')

// Expected Output from tracker.getDependencies('ComponentC'):
// {
//   dependent: 'ComponentC',
//   dependencies: []
// }

Explanation: ComponentC has no declared dependencies. The tracker returns an empty array for its dependencies.

Example 3: Circular Dependency (if detection is implemented)

// component-x.component.ts
@Component({ /* ... */ })
export class ComponentXComponent {
  constructor(
    private dependencyTracker: DependencyTrackerService,
    private componentY: ComponentYComponent // Assume ComponentY is also declared
  ) {
    this.dependencyTracker.registerDependency('ComponentX', 'ComponentY');
  }
}

// component-y.component.ts
@Component({ /* ... */ })
export class ComponentYComponent {
  constructor(
    private dependencyTracker: DependencyTrackerService,
    private componentX: ComponentXComponent // Assume ComponentX is also declared
  ) {
    this.dependencyTracker.registerDependency('ComponentY', 'ComponentX');
  }
}

// When the tracker builds its graph or performs a check:
// Expected behavior: The tracker might throw an error or return a warning indicating a circular dependency between ComponentX and ComponentY.

Explanation: ComponentX depends on ComponentY, and ComponentY depends on ComponentX, forming a cycle.

Constraints

  • All dependency declarations must be performed during the construction phase of the component or service.
  • The dependency names should be strings representing the class names of the dependencies.
  • The DependencyTrackerService should be provided at the 'root' level.
  • Performance: The dependency tracking and reporting should not significantly impact application startup time for up to 100 components/services.

Notes

  • Consider how you will resolve the string names of dependencies to actual injectable instances if you plan to implement more advanced features like dependency injection validation. For this challenge, simply tracking the string names is sufficient.
  • Think about how to structure the data within the DependencyTrackerService to efficiently store and retrieve dependency information.
  • For circular dependency detection, graph traversal algorithms like Depth First Search (DFS) can be very useful.
Loading editor...
typescript