Hone logo
Hone
Problems

Angular Dependency Injection: Implementing a Custom inject Function

Angular's dependency injection (DI) system is a cornerstone of its architecture, enabling modularity and testability. While Angular provides a built-in inject function, this challenge asks you to implement your own simplified version to deepen your understanding of how DI works under the hood. This exercise will help you grasp the core concepts of providers, injectors, and dependency resolution.

Problem Description

Your task is to implement a basic inject function that mimics the core functionality of Angular's inject. This function should take a factory function as input, which describes the dependencies required by a component or service. The inject function should resolve these dependencies from a provided injector and execute the factory function with the resolved dependencies. The injector will be a simple object containing providers, mapping tokens (strings or symbols) to their corresponding values or factory functions.

Key Requirements:

  • Factory Function: The inject function must accept a factory function as its primary argument. This factory function will receive the resolved dependencies as arguments.
  • Injector: The inject function must also accept an injector object. This object will contain the providers for the dependencies.
  • Dependency Resolution: The inject function must look up dependencies in the injector based on their tokens.
  • Provider Types: The injector should support providers that are:
    • Values: A simple value to be injected.
    • Factory Functions: A function that creates the dependency. This function will receive any dependencies it needs to create the value.
  • Circular Dependency Handling: While a full-fledged circular dependency detection is beyond the scope of this exercise, your implementation should not crash if a circular dependency is encountered. It's acceptable to return undefined or throw an error in this case.
  • Return Value: The inject function should return the value returned by the factory function.

Expected Behavior:

The inject function should resolve the dependencies specified in the factory function, inject them into the factory function, execute the factory function, and return the result. If a dependency is not found in the injector, the function should return undefined.

Examples

Example 1:

Input:
injector = {
  'hello': 'world',
  'greetingService': () => `Hello, ${inject('hello')}!`
}

inject((greetingService: string) => greetingService, injector)

Output:
"Hello, world!"
Explanation: The inject function retrieves the value associated with the 'hello' token ('world') and passes it to the factory function. The factory function then constructs the greeting string and returns it.

Example 2:

Input:
injector = {
  'logger': { log: (message: string) => console.log(message) },
  'apiService': () => ({ fetchData: () => Promise.resolve('Data!') })
}

inject(async (logger: { log: (message: string) => void }, apiService: { fetchData: () => Promise<string> }) => {
  await apiService.fetchData();
  logger.log('Data fetched successfully!');
  return 'Success';
}, injector)

Output:
(Logs to console: "Data fetched successfully!")
"Success"
Explanation: The inject function retrieves the 'logger' and 'apiService' dependencies. The factory function then uses these dependencies to fetch data and log a message. The function then returns 'Success'.

Example 3: (Edge Case - Missing Dependency)

Input:
injector = {
  'name': 'Alice'
}

inject((name: string, age: number) => `Hello, ${name}! You are ${age} years old.`, injector)

Output:
undefined
Explanation: The factory function requires a dependency 'age', which is not present in the injector. Therefore, the inject function returns undefined.

Constraints

  • The injector object will only contain string or symbol tokens.
  • The factory function can be synchronous or asynchronous (returning a Promise).
  • The inject function should not modify the injector object.
  • Performance is not a primary concern for this exercise; focus on correctness and clarity.
  • The inject function should handle cases where a provider is not found gracefully (returning undefined).

Notes

  • Consider using a recursive approach to resolve dependencies, especially when factory functions themselves require dependencies.
  • Think about how to handle the order of dependency injection. In this simplified version, the order in which dependencies are listed in the factory function is important.
  • This is a simplified implementation and does not include all the features of Angular's inject function (e.g., providers with useFactory, useClass, useExisting, etc.). The goal is to understand the core principles of dependency injection.
  • Pay close attention to TypeScript types to ensure type safety.
function inject<T>(factory: (dependencies: T) => any, injector: { [key: string]: any }): any {
  // Your code here
}
Loading editor...
typescript