DEV Community

Cover image for Angular Dependency Injection Simplified for .Net and Java Developers
Santosh Yadav for This is Angular

Posted on • Updated on

Angular Dependency Injection Simplified for .Net and Java Developers

Recently one of the .Net developer asked me the question "why we use class rather than interfaces while using Dependency Injection" the question was valid because as a .Net developer that was what we learned, Do not use or create a class instance directly.

Introduction

To be frank for me it's been more than a year I wrote .Net code, but I have written it for more than 8 years, I will copy the example from Microsoft documentation here, the below example shows how to create an instance, which is true for .Net as well as Angular.

public class IndexModel : PageModel
{
    MyDependency _dependency = new MyDependency();

    public async Task OnGetAsync()
    {
        await _dependency.WriteMessage(
            "IndexModel.OnGetAsync created this message.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Dependency Injection In .Net

The correct way to do this in .Net is

  • Define an interface
public interface IMyDependency
{
    Task WriteMessage(string message);
}
Enter fullscreen mode Exit fullscreen mode
  • Use the interface to create a new service
public class MyDependency : IMyDependency
{
    private readonly ILogger<MyDependency> _logger;

    public MyDependency(ILogger<MyDependency> logger)
    {
        _logger = logger;
    }

    public Task WriteMessage(string message)
    {
        _logger.LogInformation(
            "MyDependency.WriteMessage called. Message: {MESSAGE}", 
            message);

        return Task.FromResult(0);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Register the interface and service
services.AddScoped<IMyDependency, MyDependency>();
Enter fullscreen mode Exit fullscreen mode
  • Using the DI in another class
public class IndexModel : PageModel
{
    private readonly IMyDependency _myDependency;

    public IndexModel(IMyDependency myDependency) {
        _myDependency = myDependency;
    }

    public async Task OnGetAsync()
    {
        await _myDependency.WriteMessage(
            "IndexModel.OnGetAsync created this message.");
    }
}
Enter fullscreen mode Exit fullscreen mode

The advantage of using the above approach is we can easily replace MyDependency with another class in the future and wherever we inject IMyDependency in we get the instance of that new Service, thus avoiding any direct dependency between the service and controller.

Dependency Injection In Angular

Angular has it's on DI framework, so there is no complication of registering an interface and related service as we saw in .Net implementation

We will be looking at Class providers for this article, to create a new service we can run

ng g service <service-name>
Enter fullscreen mode Exit fullscreen mode
  • The service code looks like below
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class LoginService {
  constructor(public http: HttpClient) { }

  login(user: any) {
    console.log('login service called.');
  }
}
Enter fullscreen mode Exit fullscreen mode

I have left the login method blank just for visibility purpose, the service may have some HTTP calls as well.

  • Using it in component
import { Component }   from '@angular/core';
import { LoginService } from './login.service';

@Component({
  selector: 'app-login',
  template: `<button (click)="login()">Login<button>`
})
export class LoginComponent {

  constructor(loginService: LoginService ) {

  }

  login() {
      this.loginService.login('test');
  }

}
Enter fullscreen mode Exit fullscreen mode

The above component has loginService injected, but wait we are using the class name here rather than an interface as we do in .Net, if we are directly using the class how is it Dependency Injection, as we are using class here and why we are not using interface as it does exist in Typescript. Let's explore why and how next.

Why we cannot use Interface

The first question which comes to developers mind, who comes from .Net background is if interfaces exist why not to use interface rather than the class to achieve Dependency Injection. Let's see it programmatically why it is not possible.

  • Install typescript using
npm i typescript -g
Enter fullscreen mode Exit fullscreen mode
  • Create a Folder InterfaceDemo where we will create few files

  • Open the folder with VS Code and run below command to create tsconfig.json

tsc -init
Enter fullscreen mode Exit fullscreen mode
  • Open the tsconfig.json file and change target to ES2015.

  • Create a new file name ILogin.ts and copy the below code.

interface ILogin {
    login(user:any): any;
}
Enter fullscreen mode Exit fullscreen mode
  • From terminal run below command.
tsc
Enter fullscreen mode Exit fullscreen mode
  • Now you will get ILogin.js file open the file and see the code it's
"use strict";
Enter fullscreen mode Exit fullscreen mode

What just happened, do we mean whatever code we write inside interfaces does not exist once we get .js files?
Yes, this is what happens, typescript is used mostly for type safety they do not exist once our code is compiled to JavaScript. Now we know, why we cannot use Interfaces for DI here.

How it is Dependency Injection if We are dependent on Class Instance

Now let's explore how we are not directly dependent on LoginService even though it is injected into the component.

  • If you see the code we are not creating an instance of LoginService, we are injecting it, in the same way as we do with interface in .Net, Angular knows how to resolve the dependency, we don't need to create the instance even though injected service is dependent on another service like HttpClient in the mentioned example.

Ok agreed that we are not creating the instance, but what if I want to replace LoginService with another service let's call it as NewLoginService, we have to change the code again isn't it?
The answer is No, we don't need to let's see how we can achieve this.

  • Create NewLoginService using CLI
ng g service NewLogin
Enter fullscreen mode Exit fullscreen mode
  • Add the below code into your service.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LoginService } from './login.service';

@Injectable({
  providedIn: 'root',
})
export class NewLoginService extends LoginService {
  constructor(public http: HttpClient) { super(http); }

  login(user: any) {
     console.log('new login service called.');
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Before you make next change run the app with LoginComponent and click on the Login button and see the console we should see login service called

  • Now move to app.module.ts and you will notice providers property replace it with the below code.

providers: [{ provide : LoginService , useClass : NewLoginService }]
Enter fullscreen mode Exit fullscreen mode
  • Run the application and check the console again, and click on the Login button and see the console we should see new login service called.

This is how we can change the old service with a new service without changing even a single line of code, and this is how we can control which service should be available to the application.

Another advantage we get here if older service has 10 methods and we only want to replace 5 methods, add implementation for only 5 new methods in new service. For methods which do not exist in new service will be referred from old service, amazing isn't it?

Conclusion

The biggest mistake the developers who move to Angular make is they really get comfortable using typescript and feel home, though most of the concepts from C# is available, I always suggest developers read about Javascript as well it is really important, play with typescript try to see the output js files whenever you get time. There is really a good post form @layzee

https://dev.to/layzee/sorry-c-and-java-developers-this-is-not-how-typescript-works-401

Top comments (8)

Collapse
 
dominikpieper profile image
Dominik Pieper

Great explanation. Got the same question from a colleague today so I was able to just show him this article 😁

Collapse
 
santoshyadavdev profile image
Santosh Yadav

Glad it was helpful.

Collapse
 
naliamah1973 profile image
Nali

Thanks for this great explanation !
In the last code sample did you forget to extend LoginService ?
export class NewLoginService extends LoginService

Thanks again.

Collapse
 
santoshyadavdev profile image
Santosh Yadav

Thanks buddy, yes I missed that.

Collapse
 
carlillo profile image
Carlos Caballero

Thanks!!

Collapse
 
katnel20 profile image
Katie Nelson

Thanks Santosh, that was very clear and helpful.

Collapse
 
santoshyadavdev profile image
Santosh Yadav

Thanks a lot 😊

Collapse
 
devashishsarmah profile image
devashishSarmah

I didn't notice that Javascript doesn't include the types until now. It makes sense now why Angular uses Classes for DI