Another in our series of Angular lunch talks. Paul Spears walks through dependency injection in Angular 2.0. This covers an alpha version, the details could easily be different when the real thing ships. Recorded 2015-09-15.
We have transcribed the talk to text, provided below.
Paul: That’s me. That’s how you can get a hold of me.
For the most part, dependency injection consists of three primary parts in Angular 2. You’ve got the injectors themselves, you’ve got bindings and you’ve got the instances they create. So let’s take a look at how these all fit together. But first let’s actually talk a little bit about some of the history here.
Angular 2 was largely motivated by, and because of, this new dependency injection system. That actually was one of the first pieces that they created and said, “Hey, let’s run with this and see where it goes.”
It was a complete overhaul from the ground up from what you’re using now in Angular 1. Right now it’s actually the most mature and polished piece of Angular 2. So polished, in fact, that there’s examples of people who are taking just the core DI system out of Angular 2 and putting it in other things. Like Tero Parviainen has an example where he talks about his experience integrating it with Backbone. It’s definitely a really interesting system to take a look at outside of the context of an Angular application as well.
So, let’s talk about injectors. Whenever you bootstrap your Angular 2 application, you can actually specify that, “Hey, I’ve got some bindings to some dependencies that I need”. And whenever you run across that, that’s when Angular is creating for you a root injector. So at that point it’s saying, “Here’s a list of things that you can inject anywhere in your application.” So, it’s pretty straightforward. This MyService is written somewhere as a class in your Angular application.
Then below that layer of the initial application — initialization with Bootstrap–you can have any child component or any component of your application, then go on to create subsequent injectors that inherit from that root injector or wherever they happen to be in the component hierarchy. Those instances are called “child injectors”.
If we look at bindings now, if we look these are actually — this is pretty much the exact same code here. Back on the injectors, this isn’t actually what creates an injector. Angular automatically creates an injector every time you create a component. These, what we’re seeing, are actually bindings. These are what instruct those injectors in how to behave. So here we’re saying, “I want you to go ahead and inject MyService, and make it injectable from the root level.” And this is saying “On this component, somewhere in my application, I want you to go ahead and make MyService 2, injectable from that child injector.”
This is actually a highly sugared niceness. There are all kinds of additional pieces of functionality that you can use that give more information like, “Actually I want MyService2 injectable at this layer, under some other name, or I want you to go ahead and give it to me in some other way.” So, this is a highly sugared, nice version of it, and we’ll see a little bit more about how these pieces fit together in just a second.
You’ve got the injectors, which perform the injection. You’ve got the bindings, which tell it how. Then lastly, you end up with the actual instances that the injector creates.
Before, we had the bindings up here, and we had those instructing injectors — it’s actually not until this line right here that we actually perform an injection. It’s actually having it in the constructor and that constructor function being run, that actually goes out and injects an instance of a class.
Here it’s saying, “I want you inject MyService, however, the most recent binding and injector knows how to give it to me.” So, let’s see a bit more code, a little bit bigger examples.
Here, we’re going to start with a parent component. Pretty straight forward. Here’s our parent component, here’s how you reference that in the HTML, and here’s our bindings. We’re saying that this parent component wants to inform this injector that he’s able to inject MyUtilsService. And here, we’re requesting an instance of it. Pretty straight forward. So, let’s add another piece.
Down here, we still have our parent component doing the same thing it did before, but now it actually has a child component present. And that’s to find the top. And our child component, if you look inside our component annotation, we don’t have a bindings property. Yet at the constructor, we’re still requesting an injection of MyUtilsService. The way the dependency injection works, is that it does in fact, have its own injector. It has a child injector that was inherited from the parent injector, which in turn was inherited from the root injector. But because we didn’t specify a binding, that means it will climb the injector hierarchy looking for the first binding to MyUtilsService.
As a result, it’s going to get the exact same copy of MyUtilsService that was injected into the parent. So because we didn’t specify a binding, we’ve not given that injector any new instructions on how to go about creating that. So it used the exact same copy on the parent.
Let’s say that the parent component manipulates the state of MyUtilsService in some way that makes it just not really an option for use in a child component. Well, to achieve that, a new instance is going to be needed. We don’t actually want that, so here I’ve commented out child 1 component, we still have a parent, just the same as before, but now we’ve got a sibling to child 1, called child 2, who does exactly that by specifying the exact same binding on child component 2. We’ve now overwritten the instructions for who to go about injecting MyUtilsService.
Now, these actually refer to two different instances of MyUtilsService. Pretty simple.
Let’s say that we want child component 2 to have its unique copy, but let’s say there’s a child under child component 2. Let’s say it has a grandchild that wants to use the original copy. How do we get back to that state? Well, you might try something like this at first. You’ve got child component 2, parent is commented out, and we’ve got the grandchild.
The only thing you could possibly try is maybe by do another binding that might get it back in sync with what was up above it. No, that creates yet another instance further down. So instead, what you need to do is use the “ViewBindings” property, on child 2. So before, we were using bindings, and this is using “ViewBindings”.
ViewBindings actually takes this and says, “Okay, for this injector, here are some things that you can inject, but I want you to make them available to only the component that this is sitting on. Remove it from that hierarchy (that I referred to earlier).” So that way, if you have a grandchild, who makes a reference to MyUtilsService, because it didn’t have a binding specified, it climbs the tree. Because this is on ViewBindings, it doesn’t see this. It proceeds up the hierarchy till it gets to the parent.
I’m seeing a little bit of skeptical faces there, is that making sense?
Participant: Do you have any sense of why it works the way it does?
Paul: Why it works the way… What part of it so far? Why it creates that hierarchy…?
Participant: You know, is there anything out there that can tell us why this is believed to be the really good way for this to work? Like the word “ViewBindings”. Does that mean it’s only for the view, or does that mean it’s only for this component?
Paul: Only for that component. The actual property name here is like the least settled on part of the Angular 2 syntax. Like how DI works itself is pretty solid, but that particular “do you use ViewBindings” bindings, there were some other properties that have phased out now. So their naming there is still kind of, a little wishy-washy.
Participant: Is the intent there that this is going to be some sort of encapsulated component, kind of like isolate scope of an Angular 1 directive or is it–and I don’t mean this is the same scope, I mean like this needs to be self-contained–don’t make it part of the inheritance hierarchy. So that it doesn’t hurt anything else?
Paul: That’s the only way I’ve seen ViewBindings explained. Is in the context of removing it from the hierarchy of injected components.
Participant: So, just for me.
Paul: Right, yes.
Participant: So view is just for me. Binding means for me, and anybody inside me.
Paul: Right. Yeah. Like there’s possibly and probably some other reasoning behind that, but this is the only explanation I’ve seen given for ViewBindings so far. This kind of usage, of let’s pop this out of the tree and let anyone below me just get a parent instance of it if they need to. I’ve also seen, generally speaking, that people will actually use ViewBindings where possible. I don’t know if this is a best practice or just something that’s kind of bubbling to the top out of what people have been seeing in examples, but more often than not, I’ve seen people use ViewBindings when they can.
And that sounds like it might be a good practice off the top of my head, I’m not positive on that though.
So, you were also asking about–there’s a second part that I wanted to address–your original question about why it works this way…
Participant: Yeah, give me a sense of the rationale behind the design.
Paul: Yeah, so if you actually go check out, there’s a couple if you just google for Angular 2 dependency injection, it’s like 1 of 4 actual articles that you can find.
Yeah, the actual core DI system is really slick. In terms of its flexibility. And there’s a post from Victor… I can’t remember his last name at the moment.
Paul: Savkin, yes Victor Savkin. Explaining the problems with Angular dependency injection in Angular 1 and how this just knocks all of those straight out. It does a great job of explaining that, going through how they found their motivation for why this was a better approach. Then Thoughtram also has one that outlines even more of that.
And it just does a pretty good job of just hitting all the finer points. It’s got a lot of flexibility. What we’re seeing here is the very much sugared — this looks absolutely nothing like if you were actually using their injector system. Looks nothing like it. So this is just the thinnest layer of like, “I only want to know enough to be able to make my application work a version of it.” While you can use that injector stand-alone to, you know, fire off individual injectors all over the place, create various layers of injectors in your own class hierarchy.
And it’s got tons of flexibility behind it, and that’s kind of where the majority of the support is around that–the full picture of it, rather than just the snippet that we’re using here. So to kind of wrap up the code bit that we were talking about, so this is, in the end kind of the picture of what we created.
We created a parent component, child 1, child 2 and grandchild. Each of these actually requested an injection of MyUtils. But depending on how you injected it… Sorry. But depending upon how it was requested actually determines which instance you’re getting. Because we specify ViewBindings on child 2, that not only overrode what was in the parent but also removed it from the view of grandchild, who requested it and found its copy on parent.
That is all I have. Any questions?
Participant: Oh yeah.
Participant: Can you answer some questions?
Participant: Early on, you were talking about these multiple different levels and it’s only if on one where you do something that the injection actually occurs.
Participant: In Angular 1, if you define, say, a factory but don’t ever actually use a controller where that factory happens to be injected, that factory never gets instantiated.
Participant: What’s the equivalent here? So, at what point do we really get MyUtilsService code running?
Paul: So, I don’t know if… So here’s my parent, this is the highest point in the hierarchy in this example where we have MyUtilsService specified. I don’t know if it’s at the creation of the injector for this level, with it noticing that I send that bindings, if it actually creates it and holds it available there or if it actually delays loading that till it actually sees a request for it in the constructor. But I know this is the piece that when you’re referring to, it is injected. This is actually the injection portion of that term and this is the binding for that.
As far as which one of these signifies “Okay, go ahead and fire up the new instance.”
Participant: Constructor object.
Paul: Yeah, I’m not sure where in the timing it does that yet. Yeah.
Participant: Is this TypeScript code?
Paul: Yeah, this is all TypeScript.
Paul: Yeah, there’s–
Participant: Because you wouldn’t have the type name there for to figure out to inject, there’d have to be some other way.
Paul: Right, there’s a mechanism, and this is a couple months old now, which is as good as completely antiquated in this space. But last I looked, there’s a way to, after you have your object defined for your parent component, you can then go ahead and put some additional properties on there, and say “Okay, here’s my actual list of bindings, I essentially do what annotation does for you. Which is augment the object with additional properties.” Yeah.
Participant: I have a list.
Paul: Oh, okay. Fire away.
Participant: In Angular 1, we get really good at telling people, “All services are singletons.” End of story. No matter what you read on StackOverflow, all services are singletons.
Participant: There’s either 0 or 1 of them in the application. What’s the story here?
Paul: The responsibility is now on the consumer. If you want it to be a singleton, it can be a singleton. If you overwrite it, you create a new instance. Totally up to the user. And that’s one of the big points.
Participant: Does any of this depend on the old crazy like, “We’re going to parse the source code for the parameter list for dependency injection?” or is it like a grownup way of doing this now?
Participant: So a new level of crazy magic.
Paul: Yeah, because now you’ve got TypeScript on top of it, so who knows what, I don’t know for sure. I haven’t seen it anywhere. Because really, everything we’ve listed here is actually a reference to a thing.
Paul: Right, so at least at the TypeScript level I’ve not seen that. The ES5 though, you actually do use strings in there occasionally, so I’m thinking that this might possibly compile down into something that then turns around and has to use that string parsing thing, but I don’t think so.
Participant: Okay. Last one. Another issue with Angular 1 dependency injection is around the lack of namespacing. So if you have a customer module with a Utils service inside of it, and a products module with a Utils service inside of it, and you ask for the Utils service to be injected, well, there’s a way of knowing which one you’re going to get, but it’s hard to get to the other one.
Participant: It looks like all that is handled here, how is that?
Paul: So that’s all handled by fact — that’s just TypeScript. TypeScript in the modular system off screen here above this you’d have your “import MyUtilsService from Module A” or “import MyUtilsService from Module B” or something else entirely, you can import both of them and alias them to different names.
Participant: Do you have the ability at some much lower level than that, like “No, I explicitly want like a fully qualified class name”? Or something you can say, “I want the one from the customer module sub-section B Utils”? Or do you have to do that somewhere else and give it a unique alias throughout the application?
Paul: Yeah, that’s all part of the same module system. Your modules can have sub-modules and so on and so forth. You wouldn’t utilize that right here, but that’s totally based on how you choose to import it.
Participant: So it’s the fact that we’ve got a real module system now, we can use it how we choose.