One of the core features of Angular 2 is dependency injection. This post serves as an introduction to using DI in Angular 2 apps.
Hierarchical DI
Angular 2 uses a hierarchical system for managing injectable objects. For developers this means that the choice of having a new instance or a previously created one is up to the consumer of the injectable. Let’s see how this works. Consider the following example of a single component and a single service (service definition not shown):
@Component({ selector: 'parent-component' bindings: [MyUtilsService] }) @View({ template: '<h1>Hello World</h1>' }) class ParentComponent{ constructor(myUtils: MyUtilsService) { console.log(myUtils.getMessage()); } }
In the above sample line 3 creates a binding to “MyUtilsService”. This informs Angular that ParentComponent needs a fresh injector to provide access to MyUtilsService. The reference to MyUtilsService on line 9 is the actual request to perform injection. At this point Angular will create a new instance of MyUtilsService which is available in ParentComponent’s constructor. Let’s add another piece.
@Component({ selector: 'child-component-1', }) @View({ template: '<h2>Child 1 Content</h2>' }) class ChildComponent1{ constructor(myUtils: MyUtilsService) { //Same instance of myUtils as injected into ParentComponent console.log(myUtils.getMessage()); } } @Component({ selector: 'parent-component' bindings: [MyUtilsService] }) @View({ template: '<child-component-1></child-component-1>' directives: [ChildComponent1] }) class ParentComponent{ constructor(myUtils: MyUtilsService) { console.log(myUtils.getMessage()); } }
In this example ChildComponent1 is also referencing an injection of MyUtilsService. Since a binding was not specified for MyUtilsService on ChildComponent1 Angular injects the same instance that was created for the ParentComponent. But what if we wanted a new instance? Let’s say that ParentComponent manipulates the state of MyUtilsService in some way that makes its use undesirable in some additional child component. To achieve this a new instance would be needed instead of using the parent’s copy.
//ChildComponent1 @Component({ ... }) //ChildComponent2 @Component({ selector: 'child-component-2' bindings: [MyUtilsService] }) @View({ template: '<h2>Child 2 Content</h2>' }) class ChildComponent2{ constructor(myUtils: MyUtilsService) { //This references a different instance of myUtils from ParentComponent and ChildComponent1 console.log(myUtils.getMessage()); } } @Component({ selector: 'parent-component' bindings: [MyUtilsService] }) @View({ template: '<child-component-1></child-component-1><child-component-2></child-component-2>' directives: [ChildComponent1, ChildComponent2] }) class ParentComponent{ constructor(myUtils: MyUtilsService) { console.log(myUtils.getMessage()); } }
Now a new instance of the MyUtilsService is requested on line 9 by creating a child injector. As a result, ChildComponent2’s constructor will be supplied with a different instance than was injected into the ParentComponent. Lets add one more layer and assume that ChildComponent2 is the only component in this hierarchy in need of a separate instance of MyUtilsService. ChildComponent2’s child wants to inject the same instance that was created for and injected into ParentComponent. One may assume the following approach can be taken:
//ChildComponent1 @Component({ ... }) //ChildComponent2 @Component({ selector: 'child-component-2' bindings: [MyUtilsService] }) @View({ template: '<grandchild-component></grandchild-component>' directives: [GrandchildComponent] } //GrandchildComponent @Component({ selector: 'grandchild-component' bindings: [MyUtilsService] }) @View({ template: '<h3>Grandchild Content</h3>' }) class GrandchildComponent{ constructor(myUtils: MyUtilsService) { //This will look for and find the instance of MyUtilsService created by ChildComponent2 console.log(myUtils.getMessage()); } } //ParentComponent @Component({ ... }
However, the GrandChildComponent is receiving a third instance of MyUtilsService. This is also not ideal. To correct this and achieve the desired result let’s revisit ChildComponent2 and make a few small adjustments.
//ChildComponent1 @Component({ ... }) //ChildComponent2 @Component({ selector: 'child-component-2' viewBindings: [MyUtilsService] }) @View({ template: '<grandchild-component></grandchild-component>' directives: [GrandchildComponent] } //GrandchildComponent @Component({ selector: 'grandchild-component' }) @View({ template: '<h3>Grandchild Content</h3>' }) class GrandchildComponent{ constructor(myUtils: MyUtilsService) { //This will look for and find the instance of MyUtilsService created by ChildComponent2 console.log(myUtils.getMessage()); } } //ParentComponent @Component({ ... }
View bindings allow for the creation of injectors that will create instances of an injectable that are only available to the component they are declared on. When MyUtilsService is injected into the GrandchildComponent the DI system will climb the hierarchy looking for an injector that is aware of MyUtilsService. The instance created by ChildComponent2 is not present in the hierarchy and instead the instance created on the ParentComponent is used.
App Level Instances
In addition to creating dependency bindings at the component level it is also possible to specify a set of top level, application dependencies. This is done using the bootstrap function to initialize the application. For example:
bootstrap(ParentComponent, [MyUtilsService])
Now any component of the application can inject the same instance of MyUtilsService or request a new copy using the binding approaches listed above.
Conclusion
There is great flexibility and power in the new dependency system. This article covers some of the basics that can be used to get your application off the ground but there is still plenty more to learn about how it works under the hood. For more information about how the Angular 2 dependency injection system works check out one of the following resources:
http://victorsavkin.com/post/102965317996/angular-2-bits-unified-dependency-injection
https://angular.io/docs/js/latest/api/di/