Angular 컴포넌트에 서비스를 주입할 때 ‘Can’t resolve all parameters for component’ 오류 해결하기

1. 문제 정의

Angular 애플리케이션에서 컴포넌트의 생성자에 서비스를 주입하려는 순간, 다른 컴포넌트에는 정상적으로 주입되지만 특정 컴포넌트에서만 다음과 같은 예외가 발생하는 상황을 만날 수 있습니다.

1
EXCEPTION: Can't resolve all parameters for component

구체적인 재현 상황은 다음과 같습니다. 아래처럼 @Injectable() 데코레이터가 붙은 서비스가 있고,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { Injectable } from '@angular/core';

@Injectable()
export class MobileService {
    screenWidth: number;
    screenHeight: number;

    constructor() {
        this.screenWidth = window.outerWidth;
        this.screenHeight = window.outerHeight;
    }
}

이 서비스를 세 개의 컴포넌트에는 잘 주입하면서, 네 번째 컴포넌트에만 아래와 같이 주입하려고 하면 오류가 납니다.

1
2
3
4
5
6
7
import { Component } from '@angular/core';
import { MobileService } from './mobile.service';

@Component({ ... })
export class HeaderComponent {
    constructor(private mobileService: MobileService) { }
}

주입 대상인 서비스와 컴포넌트 모두 코드 자체에는 문제가 없어 보이는데, 왜 특정 컴포넌트에서만 주입에 실패하는지 혼란스러운 상황입니다.

항목
오류 메시지EXCEPTION: Can't resolve all parameters for component
발생 시점컴포넌트 생성자에 서비스 주입 시
특징일부 컴포넌트는 정상, 특정 컴포넌트만 실패
관련 기능Angular 의존성 주입(Dependency Injection), 배럴(barrel) 모듈

2. 원인 탐구

이 오류는 Angular가 컴포넌트의 생성자 매개변수 타입을 리플렉션(metadata)으로 읽어 의존성을 생성해야 하는데, 그 타입 정보를 제대로 해석하지 못할 때 발생합니다.

컴포넌트의 생성자 시그니처는 다음과 같습니다.

1
constructor(private mobileService: MobileService) { }

Angular는 컴파일 타임에 생성된 디자인 타입 메타데이터를 바탕으로 MobileService라는 타입을 주입합니다. 그런데 이 타입이 특정 방식으로 임포트되면 정상적으로 해석되지 못합니다.

여기서 핵심 단서는 “다른 컴포넌트에는 잘 되고 한 컴포넌트에만 안 된다"는 것입니다. 주입 실패한 컴포넌트만 서비스를 배럴(barrel) 파일을 통해 임포트하고 있었다면, 배럴 파일에서의 재수출 문제를 의심할 수 있습니다.

배럴 파일이란 여러 모듈을 한곳에서 재수출(export * from ...)하는 index.ts 파일을 말합니다. 예를 들어:

1
2
3
// components/index.ts (배럴 파일)
export * from './header.component';
export * from './mobile.service';

이렇게 배럴로 임포트할 때:

1
import { MobileService } from './components';  // 배럴 경유

직접 파일에서 임포트할 때와:

1
import { MobileService } from './components/mobile.service';  // 직접 임포트

타입 해석 경로가 달라지며, 이 차이가 오류의 원인으로 이어집니다.

3. 근본 원인 분석

StackOverflow에서 515점을 받은 채택 답변에 따르면, 이 오류의 근본 원인은 배럴(barrel) 파일의 재수출로 인한 순환 의존성(circular dependency) 입니다.

1
2
3
컴포넌트 A ──(배럴 index.ts 재수출)──▶ MobileService
    ▲                                        │
    └────────────(순환 참조)─────────────────┘

배럴 파일이 서비스와 컴포넌트를 함께 재수출할 때, Angular의 의존성 주입 메타데이터가 로드되는 과정에서 순환 참조가 발생하여 MobileService 타입이 아직 완전히 정의되지 않은 상태로 읽히고, 결국 생성자 매개변수를 해석할 수 없게 됩니다.

이것이 “코드에는 문제가 없는데 특정 컴포넌트에서만 주입이 실패하는” 이유입니다. 단순히 순수 TypeScript에서의 임포트 동작과 달리, Angular DI는 디자인 타입 메타데이터를 필요로 하기 때문에 모듈 로딩 순서(배럴 export 순서)에 민감한 것입니다.

채택 답변은 정확한 메커니즘은 불분명하다고 하면서도, 순환 의존성 문제가 반복적으로 거론된다고 설명합니다.

“I don’t know what exactly causes the issue but I saw it mentioned several times (probably some kind of circular dependency).”

즉, 근본 원인은 배럴 파일에서 서비스와 컴포넌트를 함께 재수출하면서 생기는 순환 의존성입니다.

4. 코드 해결책

이 문제를 해결하는 방법은 대표적으로 두 가지입니다.

방법 1: 서비스를 선언된 원본 파일에서 직접 임포트 (권장)

배럴 파일을 거치지 않고, 서비스가 실제로 선언된 파일에서 직접 임포트합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 수정 전: 배럴 경유로 서비스 임포트
import { Component } from '@angular/core';
import { MobileService } from './components';  // 배럴 파일 경유 — 오류 발생

// 수정 후: 선언된 원본 파일에서 직접 임포트
import { Component } from '@angular/core';
import { MobileService } from './components/mobile.service';  // 직접 임포트 — 해결

@Component({ ... })
export class HeaderComponent {
    constructor(private mobileService: MobileService) { }
}

이렇게 하면 배럴 재수출로 인한 순환 의존성을 우회하므로, Angular DI가 MobileService 타입을 정상적으로 해석할 수 있게 됩니다.

방법 2: 배럴 파일의 export 순서 조정

배럴 파일을 꼭 사용해야 한다면, 순환 의존성이 걸리는 경로를 피하도록 export 순서를 조정합니다. 예를 들어 서비스가 의존하는 타입을 먼저 export하고, 의존하는 컴포넌트를 나중에 export하는 방식입니다.

1
2
3
4
5
// components/index.ts
// 순환 의존성이 생기지 않도록 서비스를 먼저 export
export * from './mobile.service';
// 이후 컴포넌트 export
export * from './header.component';

export 순서를 바꾸어도 문제가 해결되지 않는 경우가 있으므로, 가장 확실한 해결책은 방법 1(직접 임포트) 을 적용하는 것입니다.

디버깅 표: 어떤 방법을 쓸까

상황권장 해결책
단일 컴포넌트에서만 오류, 코드 문제 없음방법 1: 서비스를 원본 파일에서 직접 임포트
배럴 파일을 유지해야 하는 경우방법 2: 배럴 export 순서 조정
여러 컴포넌트에서 반복적으로 발생배럴 패턴 자체를 점검하고 직접 임포트로 통일

5. 향후 예방 조치

이 오류를 다시 만나지 않으려면 다음 원칙을 지키면 됩니다.

  1. 배럴 파일이 서비스와 컴포넌트를 함께 재수출하는 구조를 피합니다. 서비스처럼 DI로 주입되는 타입은 배럴을 거치지 않고 원본 파일에서 직접 임포트하는 것이 안전합니다.

  2. 컴포넌트의 생성자 주입 대상은 항상 선언 파일에서 직접 임포트합니다.

    1
    2
    
    // 권장: 서비스 선언 파일에서 직접 임포트
    import { MobileService } from './mobile.service';
    
  3. 배럴을 사용할 때는 export 순서에 순환 의존성이 없는지 확인합니다. 서비스 → 컴포넌트 순서로 모듈이 로드되도록 유지합니다.

  4. “일부 컴포넌트는 되고 일부는 안 된다"는 증상이 보이면 임포트 경로 차이(배럴 vs 직접)를 우선 의심합니다. 코드 로직 문제보다 모듈 로딩 순서 문제일 가능성이 높습니다.

존재하는 모듈 구조를 유지하면서 해결책을 적용하면 대부분의 경우 즉시 해결됩니다. 핵심은 순환 의존성에 걸린 임포트 경로를 끊어 Angular DI가 서비스 타입을 정상적으로 해석하게 만드는 것입니다.


출처: StackOverflow 37997824 — Error when trying to inject a service into an angular component “EXCEPTION: Can’t resolve all parameters for component”, why?