DEV Community

Cover image for A Simple Way to Grasp Dependency Injection
Duško Perić
Duško Perić

Posted on

A Simple Way to Grasp Dependency Injection

Dependency Injection is one of those concepts that developers use every day, yet often struggle to explain clearly.
The good news is: it’s not as abstract as it sounds. If we translate it into a real-world example everything suddenly makes sense.

Dependency injection

Without Dependency Injection

Imagine you’re a sandwich seller.
A customer comes to buy a sandwich, but before you can sell it, you have to make it yourself: slice the bread, add cheese, tomatoes, lettuce...

In code, that looks something like this:

class Sandwich {
  constructor(private type: string) {}

  make(): string {
    return `Sandwich (${this.type}) is made.`;
  }
}

class Seller {
  private sandwich: Sandwich;

  constructor() {
    this.sandwich = new Sandwich("ham");
  }

  sell(): void {
    console.log(this.sandwich.make());
    console.log("Seller is selling the sandwich.");
  }
}
Enter fullscreen mode Exit fullscreen mode

Here, the seller creates the sandwich before selling it.
That means if we want to sell a veggie sandwich instead of ham, we need to go inside the Seller class and change the code.
The seller and the sandwich are tightly coupled, not great if our menu starts expanding.

With Dependency Injection

Now imagine a smarter setup: someone else is in charge of making sandwiches.
The seller simply receives a ready-made sandwich and focuses on what he does best: selling.

class Sandwich {
  constructor(private type: string) {}

  make(): string {
    return `Sandwich (${this.type}) is made.`;
  }
}

class Seller {
  constructor(private sandwich: Sandwich) {}

  sell(): void {
    console.log(this.sandwich.make());
    console.log("Seller is selling the sandwich.");
  }
}

const sandwich = new Sandwich("veggie");
const seller = new Seller(sandwich);
seller.sell();
Enter fullscreen mode Exit fullscreen mode

Now the seller doesn’t care how the sandwich was made, he just needs it to exist.
This is exactly what Dependency Injection means:

Instead of a class creating its own dependencies, it receives them from the outside.

DI in Angular

Angular takes this idea even further.
When you declare a service and inject it into a component, Angular’s DI system takes care of providing the right instance automatically.

For example:

@Injectable({ providedIn: 'root' })
export class SandwichService {
  make() {
    return 'Ham sandwich is made.';
  }
}

@Component({
  selector: 'app-seller',
  template: `
    <button (click)="sell()">Sell Sandwich</button>
    <p>{{ message }}</p>
  `
})
export class SellerComponent {
  private sandwichService = inject(SandwichService)
  message = '';

  sell() {
    this.message = this.sandwichService.make() + ' Seller is selling it.';
  }
}
Enter fullscreen mode Exit fullscreen mode

The SellerComponent doesn’t create the SandwichService, Angular does.
It just declares that it depends on it, and Angular’s injector takes care of the rest.

Dependency Injection sounds fancy, but it’s really just about delegating responsibility.
Your classes shouldn’t build the things they depend on, they should just use them.

Once you get that, you’ll see DI everywhere, not as some magical Angular mechanism, but as a simple design pattern that makes your code cleaner, more flexible, and easier to test.

So next time someone asks you what Dependency Injection is, just tell them:

It’s like being a sandwich seller who doesn’t have to make the sandwich, someone else prepares it, you just sell it.

Top comments (0)