이번 시간에는 Angular의 의존성 주입(Dependency Injection)에서 providers를 설정할 때 useClass와 useExisting의 차이를 알아보겠습니다. 이 개념은 NgModule과 Standalone Components 모두에 동일하게 적용됩니다.
useClass는 특정 토큰(token)에 대해 주입할 클래스를 지정하는 역할을 합니다. 즉, A라는 토큰을 요청했을 때, 실제로는 B라는 클래스의 새로운 인스턴스(new instance)를 생성하여 제공합니다.
사용법은 다음과 같습니다. NgModule의 providers 배열이나 Standalone Component의 providers 배열에 설정할 수 있습니다.
// In a Standalone Component's providers array
@Component({
...
standalone: true,
providers: [
{ provide: ProductService, useClass: FakeProductService }
]
})
// In an NgModule's providers array
@NgModule({
...
providers: [
{ provide: ProductService, useClass: FakeProductService }
]
})
<br/>
useExisting는 기존에 등록된 토큰을 참조하여 별칭(alias)을 지정하는 역할을 합니다. A라는 토큰을 요청했을 때, 이미 다른 토큰(B)을 위해 생성된 기존 인스턴스(existing instance)를 그대로 반환합니다.
이 방식을 사용하려면 참조할 토큰(B)이 providers 배열에 이미 등록되어 있어야 합니다.
// In a Standalone Component's providers array
@Component({
...
standalone: true,
providers: [
NewProductService,
{ provide: ProductService, useExisting: NewProductService },
]
})
// In an NgModule's providers array
@NgModule({
...
providers: [
NewProductService,
{ provide: ProductService, useExisting: NewProductService },
]
})
// test.service.ts
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class TestService {
index = 0;
add(): number {
return ++this.index;
}
}
<br/>
code
TypeScript
// new-test.service.ts
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class NewTestService {
index = 0;
add(): number {
this.index += 10;
return this.index;
}
}
이제 이 두 서비스를 주입받아 매초 호출하는 Standalone Component를 만들어 보겠습니다.
// app.component.ts
import { Component } from '@angular/core';
import { TestService } from './test.service';
import { NewTestService } from './new-test.service';
@Component({
selector: 'app-root',
standalone: true,
template: `<h1>useClass vs useExisting</h1>`,
// providers 설정은 아래 시나리오에서 변경됩니다.
})
export class AppComponent {
constructor(
private testService: TestService,
private newTestService: NewTestService
) {
setInterval(() => {
console.log('test ->', this.testService.add());
console.log('newTest ->', this.newTestService.add());
console.log('---');
}, 1000);
}
}
이제 AppComponent의 providers 설정을 바꿔가며 결과를 확인해 보겠습니다.
가장 일반적인 방법으로, 각 서비스를 개별적으로 providers에 등록해 보겠습니다. providedIn: 'root'로 이미 전역에 등록되어 있으므로 providers 배열을 비워두어도 결과는 같습니다.
// app.component.ts
@Component({
...
// providers 배열이 비어있으면 root 인젝터에서 가져옵니다.
// 명시적으로 등록한다면 아래와 같습니다.
providers: [TestService, NewTestService]
})
export class AppComponent { ... }
예상대로 두 서비스는 각각의 인스턴스를 가지며 독립적으로 동작합니다.
// result
test -> 1
newTest -> 10
---
test -> 2
newTest -> 20
---
test -> 3
newTest -> 30
---
// app.component.ts
@Component({
...
providers: [
TestService,
{ provide: NewTestService, useClass: TestService },
]
})
export class AppComponent { ... }
testService를 주입받을 때: TestService의 인스턴스가 생성됩니다.
newTestService를 주입받을 때: NewTestService 토큰에 useClass: TestService가 지정되었으므로, TestService의 새로운 인스턴스가 생성됩니다.
newTestService가 TestService처럼 동작하지만, testService와는 서로 다른 인스턴스이므로 상태를 공유하지 않습니다.
// result
test -> 1
newTest -> 1
---
test -> 2
newTest -> 2
---
test -> 3
newTest -> 3
---
// app.component.ts
@Component({
...
providers: [
TestService,
{ provide: NewTestService, useExisting: TestService },
]
})
export class AppComponent { ... }
testService를 주입받을 때: TestService의 인스턴스가 생성됩니다.
newTestService를 주입받을 때: NewTestService 토큰에 useExisting: TestService가 지정되었으므로, TestService 토큰을 위해 이미 생성된 인스턴스를 그대로 재사용합니다.
newTestService와 testService가 완전히 동일한 하나의 인스턴스를 가리키므로, 상태(index 값)를 공유합니다.
test -> 1
newTest -> 2
---
test -> 3
newTest -> 4
---
test -> 5
newTest -> 6
---