Dependency Injection is not just for unit testing
If you ask the average developer why Dependency Injection (DI) is useful, they will probably cite its utility in unit testing. And that’s true. When you are unit-testing a component, you can inject it with mock objects instead of real ones, thereby insulating your tests from the requirements and side-effects of the real versions.
Angular’s designers did make DI a central part of their story, but the reasons go beyond unit testing. In this case study, I will show how the artful use of dependency injection enables you to write Angular components that are more self-contained and reusable.
The scenario: looking up values in dependent fields
How many times have you written an application in which the user enters a value into one field, and then that value becomes the key to a lookup that populates other fields? The last two major applications I worked on each had several such scenarios. In this series, we will use an example from a grocery-store application. We’ll key in a Produce Lookup (PLU) Code and get the name of the item back, as well as an image.
To make things interesting, we will put more than one lookup on our web page. (But to avoid irrelevant complications we will put a fixed number of rows on the page.) The finished product will look like this.
(Thanks to http://supermarketpage.com/prucodes.php for the data.)
The naive approach: a page full of wire-up
A developer who does not appreciate the power of Angular’s DI framework might create the page with the following steps.
- Create a component for the page.
- Inject a produce-lookup service into the page component.
- In the page component, wire up each PLU Code field so when a new value is entered, the lookup service is called to get the corresponding name and image.
Some problems with this naive design are:
- If the Code/Name/Image trio appears elsewhere in the application, the wire-up code will have to be duplicated there.
- If other fields appear on the Produce Lookup page, and the page continues to be developed with the same design philosophy, the page will soon become overly complicated. (Yes, I say this from experience!)
- If the application has other lookups (e.g., looking up a customer’s name given their loyalty-card number), similar wire-up will have to be fashioned there, too.
“Perhaps,” the developer says, “I could avoid these problems by making an Angular component for the Code/Name/Image trio (let’s call it
PluTrioComponent), doing the wire-up there, and injecting the lookup service into that component.”
That would indeed address the first two problems but not the third problem of generalizing the lookup process. More interestingly, it would give rise to a new problem. In Angular, services are singletons. If the dependent fields are bound to the results of the service call, then every row’s dependent fields will display the same (singleton) result!
A “fix” to this that I have seen is to inject a lookup service factory into
PluTrioComponent, which would then use the factory to create a new instance of the lookup service in each component. That would work, but there is an easier way.
A better design: injection at the component level
A small tweak to the developer’s idea just described would make everything work. If you inject a service at the component level, then Angular will give you a new service instance for each instance of the component. Furthermore, when the component is destroyed, the service is destroyed along with it — something that does not happen with the service factory approach.
The remaining posts in this series will show how to do this. Along the way, we will see several other techniques that may be useful in their own right. Here is an outline of what is to come. Feel free to skip the topics that you already know well.
- How to create a mock HTTP service with Observables (Part 2)
- How to encapsulate a design pattern in an abstract service (Part 3)
- How to make an Angular attribute directive that listens for a browser event (Part 4)
- How to inject an Angular service at the right point in the component hierarchy (Part 5)
- How to modify the DOM based on an Observable (Part 6)
- Presenting the finished application (Part 7)