Angular 의존성 주입

Angular 의존성 주입 (Dependency Injection)

애플리케이션이 커질수록 여러 곳에서 같은 기능을 공유하고 싶어져요. 그런데 기능을 가진 객체를 각 클래스가 직접 만들면 재사용도 어렵고 테스트도 힘들어지죠. 의존성 주입(DI)은 객체를 외부에서 만들어 넣어주는 방식으로 이 문제를 푸는 설계 패턴이에요.

출처: 공식 문서 — Angular 의존성 주입 (Dependency injection)

DI가 왜 유용할까요

의존성 주입은 클래스가 필요로 하는 값을 스스로 만들지 않고 밖에서 공급받게 해서, 코드의 여러 부분을 더 재사용 가능하고 관리하기 쉽게 만들어줘요. 대표적인 이점은 이 세 가지예요.

  • 유지보수성 향상 — 관심사 분리가 명확해져 리팩터링이 쉬워지고 중복이 줄어요
  • 확장성 — 모듈화된 기능을 애플리케이션의 여러 부분에서 재사용하기 좋아져요
  • 더 나은 테스트 — 단위 테스트에서 실제 구현 대신 테스트 더블(test double)을 넣을 수 있어요

Angular에서 DI는 어떻게 동작하나요

의존성(dependency)이란 클래스가 동작하는 데 필요하지만 스스로 만들지 않는 모든 값이에요. 객체, 값, 함수, 서비스가 될 수 있어요. 주입 시스템을 쓰는 방식은 크게 두 가지예요.

  • 제공(provide) — 값을 사용할 수 있게 만들기
  • 주입(inject) — 그 값을 의존성으로 요청하기

여기서 "값"은 객체·함수·클래스 인스턴스 같은 어떤 자바스크립트 값이든 될 수 있어요. 흔히 주입되는 의존성 종류는 환경별 상수처럼 쓰이는 설정 값, 런타임 조건에 따라 객체를 만드는 팩토리, 공통 기능이나 상태를 담는 서비스예요. Angular 컴포넌트와 디렉티브는 자동으로 DI에 참여하므로, 의존성을 주입받을 수도 있고 주입 가능한 값으로 노출할 수도 있어요.

서비스(Service)란 무엇인가요

앵귤러 서비스는 @Service 데코레이터를 붙인 타입스크립트 클래스예요. 이 데코레이터 덕분에 클래스 인스턴스를 의존성으로 주입할 수 있게 되죠. 서비스는 애플리케이션 전반에 걸쳐 데이터와 기능을 공유하는 가장 흔한 방법이에요.

주로 이런 역할을 맡아요.

  • 데이터 클라이언트 — 서버 요청을 통한 데이터 조회·변경의 세부 사항을 추상화
  • 상태 관리 — 여러 컴포넌트나 페이지가 공유하는 상태 정의
  • 인증·권한 — 사용자 인증, 토큰 저장, 접근 제어 관리
  • 로깅·오류 처리 — 로깅이나 오류 상태 전달의 공통 API 구축
  • 이벤트 처리·디스패치 — 특정 컴포넌트에 묶이지 않은 이벤트를 처리하거나 옵저버 패턴에 따라 디스패치
  • 유틸리티 함수 — 데이터 포맷·검증·계산 같은 재사용 함수 제공

참고로 @Service 데코레이터는 @Injectable({providedIn: 'root'})의 축약형이에요. 이렇게 하면 서비스가 애플리케이션 전체에서 싱글턴으로 사용 가능해지고, 대부분의 서비스에서 권장되는 방식이에요.

inject() 함수로 의존성 주입하기

의존성은 Angular의 inject() 함수로 주입받을 수 있어요. 아래 네비게이션 바 예시는 AnalyticsLogger와 Angular의 Router 서비스를 주입받아, 페이지 이동을 하면서 이벤트를 추적하는 모습이에요.

import { Component, inject} from '@angular/core';
import { Router } from '@angular/router';
import {AnalyticsLogger} from './analytics-logger';

@Component({
  selector: 'app-navbar',
  template: `<a href="#" (click)="navigateToDetail($event)">Detail Page</a>`,
})
export class Navbar {
  private router = inject(Router);
  private analytics = inject(AnalyticsLogger);

  navigateToDetail(event: Event) {
    event.preventDefault();
    this.analytics.trackEvent('navigation', '/details');
    this.router.navigate(['/details']);
  }
}

inject()는 어디에서 쓸 수 있나요

inject()는 컴포넌트·디렉티브·서비스의 생성 시점에 호출할 수 있어요. 호출 위치는 생성자(constructor)나 필드 초기화 구문 둘 다 가능해요.

@Component(/* ... */)
export class MyComponent {
  // ✅ 클래스 필드 초기화에서
  private service = inject(MyService);

  // ✅ 생성자 본문에서
  private anotherService: MyService;
  constructor() {
    this.anotherService = inject(MyService);
  }
}
@Directive({ ... })
export class MyDirective {
  // ✅ 클래스 필드 초기화에서
  private element = inject(ElementRef);
}
import { Service, inject} from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Service()
export class MyService {
  // ✅ 서비스 안에서
  private http = inject(HttpClient);
}
export const authGuard = () => {
  // ✅ 라우트 가드 안에서
  const auth = inject(AuthService);
  return auth.isAuthenticated();
};

이처럼 inject()를 호출할 수 있는 위치를 통틀어 **인젝션 컨텍스트(injection context)**라고 불러요. 컴포넌트·디렉티브·서비스의 생성이 가장 흔하지만, 그 외의 컨텍스트도 있어요.

더 알아보기 (Learn more)