Back to Blog
Angular DI Essentials: What Changed the Game for Me
angulartypescriptfrontenddependency-injectionsoftware-engineering

Angular DI Essentials: What Changed the Game for Me

Most Angular devs use Dependency Injection every day without fully understanding it. Here are the key concepts that levelled up my Angular architecture.

Angular DI Essentials: What Changed the Game for Me

Most Angular devs use Dependency Injection every day without fully understanding it. Here's what changed the game for me 👇

✅ Services Are Your Modular Units

Services decorated with @Injectable are your modular units — keep business logic OUT of components. Common service patterns include data clients, state management, authentication, and logging.

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

@Injectable({ providedIn: 'root' })
export class UserService {
  private users: User[] = [];

  getUsers(): User[] {
    return this.users;
  }

  addUser(user: User): void {
    this.users = [...this.users, user];
  }
}

Your components should be thin — just wiring up templates to services:

import { Component, inject } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-user-list',
  template: `
    @for (user of users; track user.id) {
      <div class="user-card">{{ user.name }}</div>
    }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserListComponent {
  private userService = inject(UserService);
  users = this.userService.getUsers();
}

✅ The Injector Hierarchy Controls Instance Scope

The injector hierarchy (App → Route → Component) controls instance scope — one wrong level = shared state bugs.

// Root-level: ONE instance for the entire app
@Injectable({ providedIn: 'root' })
export class GlobalStateService {
  count = signal(0);
}

// Component-level: NEW instance per component
@Component({
  selector: 'app-counter',
  providers: [CounterService], // Each component gets its own instance
  template: `<span>{{ counter.value() }}</span>`,
})
export class CounterComponent {
  counter = inject(CounterService);
}

✅ inject() Over Constructor Injection

inject() over constructor injection = cleaner, more composable code. The inject() function works in field initializers, constructors, and even route guards:

// ❌ Old constructor injection
export class OldComponent {
  constructor(
    private userService: UserService,
    private router: Router,
    private http: HttpClient,
  ) {}
}

// ✅ Modern inject() approach
export class ModernComponent {
  private userService = inject(UserService);
  private router = inject(Router);
  private http = inject(HttpClient);
}

It also unlocks powerful patterns in functional guards and resolvers:

export const authGuard = () => {
  const auth = inject(AuthService);
  const router = inject(Router);

  if (auth.isAuthenticated()) {
    return true;
  }
  return router.navigate(['/login']);
};

✅ InjectionTokens Unlock Tree-Shaking

InjectionTokens unlock tree-shaking — unused code gets dropped from your bundle automatically.

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

export interface AppConfig {
  apiUrl: string;
  enableAnalytics: boolean;
  maxRetries: number;
}

export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG', {
  providedIn: 'root',
  factory: () => ({
    apiUrl: 'https://api.myapp.com',
    enableAnalytics: true,
    maxRetries: 3,
  }),
});

// Usage in a service
@Injectable({ providedIn: 'root' })
export class ApiClient {
  private config = inject(APP_CONFIG);

  fetch(endpoint: string) {
    return `${this.config.apiUrl}/${endpoint}`;
  }
}

✅ skipSelf and host for Precision

skipSelf and host give you precision when the default resolution isn't enough.

@Component({
  selector: 'app-panel',
  providers: [PanelService],
  template: `<app-panel-header />`,
})
export class PanelComponent {
  panel = inject(PanelService); // Gets its own instance
}

@Component({
  selector: 'app-panel-header',
  template: `<h2>{{ panel.title() }}</h2>`,
})
export class PanelHeaderComponent {
  // Skip own injector, use parent's PanelService instance
  panel = inject(PanelService, { skipSelf: true });
}

DI in Angular is genuinely one of the most well-engineered parts of the framework. The more you understand it, the more intentional your architecture becomes.

Save this if you're levelling up your Angular skills. 🚀

Related Posts

Angular Dependency Injection: A Visual Deep Dive

Angular Dependency Injection: A Visual Deep Dive

A comprehensive slide-deck breakdown of Angular DI — from @Injectable to InjectionTokens, injector hierarchy, and advanced resolution strategies like skipSelf and host.

angulartypescriptfrontend+2 more
Read More
Mastering Angular Components: The Building Blocks

Mastering Angular Components: The Building Blocks

A deep dive into Angular components — from structure and composition to lifecycle hooks, change detection, and DOM interactions.

angularfrontendui-architecture+2 more
Read More
Angular Signals: Reactive State Made Easy

Angular Signals: Reactive State Made Easy

Angular Signals make reactive state easier to reason about. A breakdown of writable, computed, linked, and effect-based primitives for precise dependency tracking.

angularsignalsfrontend+2 more
Read More

Design & Developed by Sachin Singh
© 2026. All rights reserved.