Replace Angular DI Constructor Injection with the inject function using Angular Migrations

Tomasz Ducin
InstructorTomasz Ducin
Share this video with your friends

Social Share Links

Send Tweet

Use automatic migration to make your angular codebase use the new inject-based dependency injection. Or follow a manual step-by-step migration, if needed.

Angular developers can benefit from using inject-based dependency injection through its simplified syntax, improved type safety, and seamless integration with decorators.

Instead of relying on constructor parameters, developers can use the inject() function directly where a dependency is needed, resulting in more accurate type checking and early error detection during development.

The inject() function works smoothly with Angular decorators like @Host, @Self, and @Optional, providing better control over dependency resolution.

The Angular CLI offers an automated migration schematic (ng generate @angular/core:inject) that simplifies the transition from constructor-based injection to using inject(). This schematic automatically converts existing code, minimizing manual effort and ensuring a smoother migration process.

[00:00] Modern Angular has introduced the inject function, which replaces the good old constructor injection. Inject function is slightly more flexible comparing to the constructor injection, thanks to its functional nature. Before running the migration, let's see a manual migration. So in this case, we are injecting [00:20] the benefit service of the benefit service class, and we make it a private property. So let's comment out this line. So just to keep things exactly the same as they were before, we're creating a private property. And what we want to do is to inject using [00:39] the function imported from Angular core, and we inject simply by a token. So in a very simple use case, there is pretty much nothing more to do. But in more complex scenarios such as this one, there is a lot of things going on. So let's run the migration. And that is going to be [00:59] NG generate and, again, Angular core package. And what we're going to migrate is the inject function. So in this case, we're going to receive quite a bit of the questions from Angular migration, which I'm going to deliberately ignore for now. We'll get back to them later. And we can see that there [01:19] is quite a bit of files that have been modified since each of these files is using dependency injection. So let's open the expenses listing component in a diff mode so that we can see what has been modified. We can see the Angular [01:39] decorators which alter the way the dependency injection mechanism work. So the optional self, skip self, and host have been migrated into the second parameter of the inject function, which is the optional self skip self and, respectively, the host attributes, which are [01:59] going to be modified into true. Of course, if we have multiple decorators, then accordingly, the parameters are going to be put over here. Now the inject of a d a token, this d a token is simply a new injection token of a type string in [02:18] angular is basically migrated into an inject call with the d a token. And finally, we have the inject of a setting service token, which is important from another file. So let's take a look at what is the definition. So the setting service is [02:38] an abstract class that is being defined under a setting service token, which is yet again a new injection token. So what we can see over here is that the setting service token, due to the fact that this token is defined [02:58] as a setting service, so not a primitive type, but an object type. Here, the migrated version also highlights what is the expected type over here. So let's go back now to the questions that I have ignored in the comment line. So let's just clean the situation and get back to the [03:18] point where we started. So let's again run the NGGEN rate Angular core inject. So this one will ignore, ignore, ignore. And do you want optional inject calls to be non nullable? Enable this option if you want the return type to be identical to optional at the expense of worst type safety. So what is important [03:38] is that when we use the optional decorator, then benefit service could be injected and that would be okay. But if it was not found within the injector car key, it would also be okay. And Angular would return null in runtime. But the thing is, if we take a look at the original file, we will [03:58] see that TypeScript does not infer that this is either the benefit service or null. TypeScript claims that this is going to be benefit service or actually Angular. So the thing is that if we make it to be identical to what optional was returning. So let's [04:18] put yes over here. This inject call is going to be suffixed with an exclamation mark, which basically says that, hey, this thing needs to be here even if it could not be here. So normally, the benefit service, in this case, if this is optional, it could be [04:37] either the benefit service or null because the thing might not be here. However, with this option run, then this will be enforced for the thing to artificially be guaranteed. So what the question was asking was that at the expense of worst type safety. So in this case, we [04:57] are obviously worsening the type safety since TypeScript claims the thing is here even though in runtime it could not be. Let's also revisit the other two options. So let's run the migration again and this time we're going to concentrate on another question, which is, do you want to clean up [05:17] all the constructors? Which means, since we're going to use inject, the existing constructor is not needed anymore. Or do you want to keep them backwards compatible? This is in case you have been using you have been calling the constructor manually anywhere within your application, because if constructor is going to be [05:37] removed, then your manual constructor calls would obviously fail. Now enabling this option will include an additional signature, and this signature of constructor allows us to pass basically unlimited number of anything, which might not be type safe. That will avoid errors for subclasses, [05:57] but will increase the amount of generated code by the migration. So all in all, we're going to leave a backdoor for any type of a way to call the constructor, just in case that you have been using them manually anywhere. So this is the constructor that is remaining, [06:16] and this is the overload. So this is useful only in case you have been using the constructor manually. Otherwise, it makes no sense to use this one. So the final question relates to abstract classes. And in our example, the setting service is an abstract class that would eventually [06:37] implement an injectable service. So as you can see, this is already migrated. So by default, abstract classes do not get migrated. So now let's reset our repository to git checkout and let's once again run the Angular core. [06:56] Generate inject and we're going to run it on everything. Now do you want to migrate abstract classes? Yes. Abstract classes are not migrated by default because their parameters aren't guaranteed to be injectable. That's basically a limitation of the framework. And we're running no no no for everything else. [07:16] So the thing is, if you're using for any reason abstract classes, you need to make sure to turn this option on. As we can see, that's basically being migrated the same way as in all other examples.