Angular Module Injector Implementation
This challenge asks you to implement a simplified module injector for Angular, mimicking the core functionality of Angular's dependency injection system. Building a module injector is a fundamental exercise in understanding how Angular manages dependencies and promotes modularity, providing a deeper understanding of the framework's inner workings.
Problem Description
You are tasked with creating a basic module injector that can register and resolve dependencies within a given module. The injector should be able to register services (classes) and resolve them when requested. The injector should support both class-based and function-based providers.
What needs to be achieved:
- Create a
ModuleInjectorclass. - Implement a
registermethod that allows registering services with the injector. This method should accept a service identifier (string) and either a class constructor or a factory function. - Implement a
resolvemethod that retrieves a registered service by its identifier. If the service is a class, the injector should create an instance of the class using its constructor. If it's a factory function, the injector should execute the function and return its result. - Handle cases where a service is not registered.
Key Requirements:
- The
ModuleInjectorshould be able to handle both class-based and function-based providers. - When resolving a class-based service, the injector should create a new instance of the class.
- The
resolvemethod should throw an error if the requested service is not registered. - The injector should not modify the registered providers.
Expected Behavior:
registershould successfully register services.resolveshould return an instance of the registered class or the result of the factory function.- Attempting to
resolvean unregistered service should throw an error.
Edge Cases to Consider:
- What happens if the class constructor requires dependencies that are not registered in the injector? (For simplicity, assume that dependencies are not required for this challenge. The constructor should be invoked without any arguments.)
- What happens if the factory function throws an error? (The error should be propagated.)
- What happens if the same service is registered multiple times? (The last registration should take precedence.)
Examples
Example 1:
Input:
injector = new ModuleInjector();
injector.register('logger', Logger);
const logger = injector.resolve('logger');
class Logger {
log(message: string) {
console.log(message);
}
}
Output:
logger.log("Test message"); // Output: "Test message" in console
Explanation: The 'logger' service (Logger class) is registered and then resolved. The resolved service is an instance of the Logger class.
Example 2:
Input:
injector = new ModuleInjector();
injector.register('apiService', () => new ApiService());
class ApiService {
getData() {
return "Data from API";
}
}
const apiService = injector.resolve('apiService');
Output:
apiService.getData(); // Output: "Data from API"
Explanation: The 'apiService' is registered as a factory function. The factory function creates an instance of ApiService, which is then returned by the resolve method.
Example 3: (Edge Case - Unregistered Service)
Input:
injector = new ModuleInjector();
injector.register('logger', Logger);
try {
injector.resolve('nonExistentService');
} catch (error) {
console.error(error.message);
}
class Logger {
log(message: string) {
console.log(message);
}
}
Output:
console.error("Service 'nonExistentService' not found");
Explanation: Attempting to resolve a service that hasn't been registered throws an error.
Constraints
- The
ModuleInjectorclass should be implemented in TypeScript. - The service identifier must be a string.
- The registered class must be a valid TypeScript class.
- The factory function must be a function that returns an object.
- The injector should not use any external libraries beyond standard TypeScript.
- Performance is not a primary concern for this simplified implementation.
Notes
- This is a simplified module injector and does not include all the features of Angular's dependency injection system (e.g., providers, multi-providers, hierarchical injectors).
- Focus on the core functionality of registering and resolving services.
- Consider using a simple JavaScript object (or Map) to store the registered services.
- Think about how to handle errors gracefully.
- The goal is to demonstrate an understanding of the basic principles of dependency injection.