Forgive me for not understanding this if it's been said.
Why does the Angular implementation of its Dependency Injector use the injectables through a
constructor
?
I am used to seeing a DI
in various ways. Even a static method would make sense (If that exists sorry I haven't dug that deep yet, I'm a week into it so far).
Wouldn't it be easier or more logical to use it this way, more similar to a DI we see more often but still passing it in the constructor?:
// Non-Angular Example
@Component({})
class FooComponent {
public appState: AppState;
constructor(DI: DependencyInjector) {
this.appState = DI.get('AppState');
}
ngOnInit() {}
}
Angular is more like this, I'm not sure if it's for verbosity only, or if there are other reasons.
// Angular 2/4 Example
@Component({})
class BarComponent {
public appState: AppState;
constructor(appState: AppState,
router: Router,
etc: EtcSomething) {
}
ngOnInit() {}
I know Google had thought of this, I am just trying to understand the reasoning and/or benefit. Perhaps I woke up thinking about silly things and it's obvious and just went over my head but I missed it.
I hope what I'm asking makes sense, I just wonder why.
Dependency injection, or DI, is one of the fundamental concepts in Angular. DI is wired into the Angular framework and allows classes with Angular decorators, such as Components, Directives, Pipes, and Injectables, to configure dependencies that they need.
The goal of the dependency injection technique is to remove this dependency by separating the usage from the creation of the object. This reduces the amount of required boilerplate code and improves flexibility.
Wouldn't it be easier or more logical to use it this way, more similar to a DI we see more often but still passing it in the constructor?
The illustration of the pattern in your example is actually called a Service locator pattern. This pattern is considered by many to be an anti-pattern.
Advantages
- The "service locator" can act as a simple run-time linker. This allows code to be added at run-time without re-compiling the application, and in some cases without having to even restart it.
- Applications can optimize themselves at run-time by selectively adding and removing items from the service locator. For example, an application can detect that it has a better library for reading JPG images available than the default one, and alter the registry accordingly.
- Large sections of a library or application can be completely separated. The only link between them becomes the registry.
Disadvantages
- Things placed in the registry are effectively black boxes with regards to the rest of the system. This makes it harder to detect and recover from their errors, and may make the system as a whole less reliable.
- The registry must be unique, which can make it a bottleneck for concurrent applications.
- The registry can be a serious security vulnerability, because it allows outsiders to inject code into an application.
- The registry hides the class' dependencies, causing run-time errors instead of compile-time errors when dependencies are missing.
- The registry makes the code more difficult to maintain (opposed to using Dependency injection), because it becomes unclear when you would be introducing a breaking change.
- The registry makes code harder to test, since all tests need to interact with the same global service locator class to set the fake dependencies of a class under test. However, this is easily overcome by injecting application classes with a single service locator interface.
The (preferred) pattern currently used by angular
(as well as other frameworks).
Advantages
- Dependency injection allows a client the flexibility of being configurable. Only the client's behavior is fixed. The client may act on anything that supports the intrinsic interface the client expects.
- Dependency injection can be used to externalize a system's configuration details into configuration files, allowing the system to be reconfigured without recompilation. Separate configurations can be written for different situations that require different implementations of components. This includes, but is not limited to, testing.
- Because dependency injection doesn't require any change in code behavior it can be applied to legacy code as a refactoring. The result is clients that are more independent and that are easier to unit test in isolation using stubs or mock objects that simulate other objects not under test. This ease of testing is often the first benefit noticed when using dependency injection.
- Dependency injection allows a client to remove all knowledge of a concrete implementation that it needs to use. This helps isolate the client from the impact of design changes and defects. It promotes reusability, testability and maintainability.
- Reduction of boilerplate code in the application objects, since all work to initialize or set up dependencies is handled by a provider component.
- Dependency injection allows concurrent or independent development. Two developers can independently develop classes that use each other, while only needing to know the interface the classes will communicate through. Plugins are often developed by third party shops that never even talk to the developers who created the product that uses the plugins.
- Dependency Injection decreases coupling between a class and its dependency.
Disadvantages
- Dependency injection creates clients that demand configuration details be supplied by construction code. This can be onerous when obvious defaults are available.
- Dependency injection can make code difficult to trace (read) because it separates behavior from construction. This means developers must refer to more files to follow how a system performs.
- Dependency injection typically requires more upfront development effort since one can not summon into being something right when and where it is needed but must ask that it be injected and then ensure that it has been injected.
- Dependency injection forces complexity to move out of classes and into the linkages between classes which might not always be desirable or easily managed.
- Ironically, dependency injection can encourage dependence on a dependency injection framework.
You can find articles, books, tutorials, and additional information on these patterns. Although it is a matter of preference there is a consensus of preferring Dependency injection over Service locator pattern.
There are also other similar questions about the differences between these 2 like this one: What's the difference between the Dependency Injection and Service Locator patterns?.
Constructor injection is the simplest and sanest way to implement Dependency Injection.
Even when this is not always the most convenient for the user because it has certain limitations, like circular dependencies not supported and superclasses need to get all parameters forwarded from subclasses.
Every other way would make it difficult to know when exactly the dependencies are available and can be accessed. Additional lifecycle callbacks would be needed that are called after the dependencies were injected.
Object creation with inheritance is quite difficult so that the classes are constructed from the inside (superclass) out (subclass) so that each level has a properly initialized state where it several preconditions hold, like that no subclass accesses a member before the superclass constructor isn't completed.
Typescript (ES6) for example enforces to call super()
in the constructor if there is a non-generic constructor in the superclass.
If there is a constructor present in sub-class, it needs to first call super() before using "this".
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With