Advanced Dependency Injection

Back

Loading concept...

🏰 Advanced Dependency Injection in Angular

A Story of the Smart Castle and Its Helpers


Imagine you live in a magical castle. This castle has many rooms, and each room has helpers (like cooks, cleaners, and guards). But here’s the cool part: not every room needs to find its own helpers. The castle has a smart system that knows exactly which helper to send to which room!

In Angular, this smart system is called Dependency Injection (DI). Today, we’re going to explore the advanced secrets of how this system works.


🏗️ The Injector Hierarchy

How the Castle Organizes Its Helpers

Think of the castle as having floors. The King lives on the top floor. Below him are Princes. Below them are Knights.

graph TD A["👑 Root Injector<br/>#40;The King#41;"] --> B["🏠 Module Injector<br/>#40;The Prince#41;"] B --> C["🧩 Component Injector<br/>#40;The Knight#41;"] C --> D["📄 Element Injector<br/>#40;The Page#41;"]

Here’s how it works:

  1. When a room (component) needs a helper, it first asks its own floor (local injector).
  2. If that floor doesn’t have the helper, it asks the floor above (parent injector).
  3. This keeps going up until it reaches the King (root injector).

Simple Example

// The King provides a royal chef
@NgModule({
  providers: [ChefService]
})
export class AppModule {}

// The Knight's room uses the same chef
@Component({
  selector: 'kitchen'
})
export class KitchenComponent {
  constructor(private chef: ChefService) {
    // Gets the royal chef from above!
  }
}

Real Life: It’s like asking your mom for cookies. If she doesn’t have any, she asks grandma. Grandma always has cookies! 🍪


🤷 Optional Dependency

“It’s Okay If You Don’t Have It!”

Sometimes a room needs a helper, but it’s totally fine if that helper isn’t available.

Imagine asking: “Is there a musician in the castle? If yes, great! If no, I’ll just hum to myself.”

How to Say “It’s Optional”

import { Optional } from '@angular/core';

@Component({
  selector: 'party-room'
})
export class PartyComponent {
  constructor(
    @Optional() private musician: MusicianService
  ) {
    if (this.musician) {
      this.musician.playMusic();
    } else {
      console.log('No musician? I will hum!');
    }
  }
}

What happens:

  • âś… If MusicianService exists → you get the musician
  • âś… If it doesn’t exist → you get null (no error!)
  • ❌ Without @Optional() → the app would crash!

🪞 Self Dependency

“Only Look in MY Room!”

Sometimes you want to be very specific: “I only want a helper that’s right here in my room. Don’t go asking upstairs!”

Think of a child saying: “I want MY teddy bear, not my brother’s!”

How to Use Self

import { Self } from '@angular/core';

@Component({
  selector: 'my-room',
  providers: [TeddyBearService]
})
export class MyRoomComponent {
  constructor(
    @Self() private teddy: TeddyBearService
  ) {
    // Only gets teddy from THIS component
  }
}

What happens:

  • âś… If TeddyBearService is provided right here → you get it
  • ❌ If it’s only provided upstairs → ERROR! (won’t look up)

⬆️ SkipSelf Dependency

“Ask Upstairs, Not Here!”

The opposite of @Self(). It says: “Don’t look in my room. Go ask my parents!”

Think of a child saying: “I don’t want MY broken toy. I want dad’s cool gadget!”

How to Use SkipSelf

import { SkipSelf } from '@angular/core';

@Component({
  selector: 'child-room',
  providers: [ToyService] // Has its own toy
})
export class ChildComponent {
  constructor(
    @SkipSelf() private toy: ToyService
  ) {
    // Ignores local ToyService
    // Gets ToyService from parent instead!
  }
}

Why is this useful?

  • When you want to access a parent’s version of something
  • When building things like tree structures where each level needs to know about the level above

🏠 Host Dependency

“Only Ask My Direct Boss!”

@Host() is special. It says: “Look for the helper, but stop at my host component. Don’t go any higher!”

Think of it like this: You can ask your teacher, but NOT the principal.

graph TD A["🏫 Principal<br/>#40;Parent Component#41;"] --> B["👩‍🏫 Teacher<br/>#40;Host Component#41;"] B --> C["📝 Template<br/>#40;You are here#41;"]

How to Use Host

import { Host } from '@angular/core';

@Directive({
  selector: '[myHighlight]'
})
export class HighlightDirective {
  constructor(
    @Host() private logger: LoggerService
  ) {
    // Only looks up to the HOST component
    // Won't go beyond that!
  }
}

When to use:

  • In directives that need services from their host component
  • When you want to limit how far Angular searches

đź’‰ The inject() Function

A Modern Way to Get Helpers

Angular has a newer, cleaner way to ask for helpers: the inject() function!

Instead of putting everything in the constructor, you can use inject() anywhere in specific places.

Old Way (Constructor)

@Component({ selector: 'app-hero' })
export class HeroComponent {
  constructor(
    private heroService: HeroService,
    private logger: LoggerService
  ) {}
}

New Way (inject function)

import { inject } from '@angular/core';

@Component({ selector: 'app-hero' })
export class HeroComponent {
  private heroService = inject(HeroService);
  private logger = inject(LoggerService);
}

Why is this cool?

  • âś… Cleaner code
  • âś… Works in functions, not just classes
  • âś… Easier to read

Using inject() with Options

// Optional injection
const music = inject(MusicService, { optional: true });

// Self injection
const local = inject(LocalService, { self: true });

// SkipSelf injection
const parent = inject(ParentService, { skipSelf: true });

// Host injection
const host = inject(HostService, { host: true });

🌍 Injection Context

“When and Where Can I Ask for Helpers?”

The inject() function is powerful, but it has one important rule: You can only use it in special places called an injection context.

Think of it like a phone: You can only make calls when you have signal. No signal = no calls!

Where You HAVE Injection Context âś…

// 1. In constructor
constructor() {
  const service = inject(MyService); // âś… Works!
}

// 2. In field initializers
private service = inject(MyService); // âś… Works!

// 3. In factory providers
{
  provide: MyToken,
  useFactory: () => {
    const helper = inject(HelperService); // âś… Works!
    return new MyToken(helper);
  }
}

Where You DON’T Have Context ❌

// ❌ Inside regular methods - NO!
onClick() {
  const service = inject(MyService); // ❌ ERROR!
}

// ❌ Inside setTimeout - NO!
setTimeout(() => {
  const service = inject(MyService); // ❌ ERROR!
}, 1000);

The Magic Fix: runInInjectionContext()

What if you NEED to use inject() somewhere else? Angular has a solution!

import {
  inject,
  Injector,
  runInInjectionContext
} from '@angular/core';

@Component({ selector: 'app-demo' })
export class DemoComponent {
  private injector = inject(Injector);

  onClick() {
    runInInjectionContext(this.injector, () => {
      // âś… Now inject() works here!
      const service = inject(MyService);
      service.doSomething();
    });
  }
}

🎯 Putting It All Together

Here’s a complete example using everything we learned:

import {
  Component,
  Optional,
  Self,
  SkipSelf,
  Host,
  inject
} from '@angular/core';

@Component({
  selector: 'smart-room',
  providers: [LocalGuard]
})
export class SmartRoomComponent {
  // Modern inject() syntax
  private logger = inject(LoggerService);

  constructor(
    // Optional: okay if missing
    @Optional() private music: MusicService,

    // Self: only from this component
    @Self() private localGuard: LocalGuard,

    // SkipSelf: only from parent
    @SkipSelf() private parentConfig: ConfigService,

    // Host: only up to host component
    @Host() @Optional() private theme: ThemeService
  ) {
    this.logger.log('Room is ready!');
  }
}

🌟 Quick Summary

Decorator What It Does Real-Life Analogy
@Optional() “Give me null if not found” “Got cookies? No? That’s okay!”
@Self() “Only look in MY room” “I want MY teddy, not yours!”
@SkipSelf() “Ask my parents, not me” “I want DAD’S cool gadget!”
@Host() “Only ask up to my boss” “Ask teacher, not principal”
inject() Modern way to get services A simpler phone call
Injection Context Where inject() works Having phone signal

🎉 You Did It!

You now understand the advanced secrets of Angular’s Dependency Injection!

Remember:

  • The castle (injector hierarchy) always searches up by default
  • You can control WHERE to look with @Self(), @SkipSelf(), and @Host()
  • You can say “it’s okay if missing” with @Optional()
  • The inject() function is the modern, clean way to get services
  • Always check you’re in an injection context before using inject()

Now go build amazing Angular apps! 🚀

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.