앵귤러에서 항상 신기하게 생각하던 코드가 있었는데 바로 이 부분이다.
@Component({ ... })
class MyComponent {
constructor(private myService: MyServce) {
myService.doSomething()
}
}
왜 신기했냐면 나는 이 컴포넌트에 어떠한 방식으로든 생성자의 파라미터에 값을 넣어주지 않았기 때문이다. 생성자에 값을 선언하는 것 만으로 어떻게 되는걸까? 리액트라면 어떠한 형태로든 내가 값을 넣어주어야 했는데, 앵귤러에서는 이미 어떠한 형태로 넣어 주게끔 만들어져 있다.
Injectable
앵귤러에서는 Service
라는 개념을 두어서 컴포넌트에서 직접 로직을 처리하는 것 보다 서비스에서 처리하도록 하는 것을 권장하고 있다. (백엔드에서는 주로 사용되는 개념인 듯 한데 나에겐 좀 생소하게 느껴졌다.) 재사용성을 높히고 컴포넌트에서는 사용자와의 상호작용에 대해서만 신경쓰도록 해서 관심사를 분리하려는 목적으로 보인다.
서비스를 구현하는 방법은 다음과 같다.
@Injectable({
providedIn: 'root'
})
class MyService {
doSometing() {
console.log('Hello, My Service.')
}
}
providedIn: 'root'
이 부분은 해당 인스턴스가 존재하는 계층에 대한 표기인데, 앵귤러에서는 해당 클래스의 인스턴스가 불필요하게 생성되지 않도록 내부적으로 인스턴스를 필요한 만큼만 생성하여 관리한다. 위와같이 root
계층에 존재하도록 명시하면 루트 계층에서 싱글톤으로 유지되며 사용하지 않는 인스턴스는 알아서 정리해준다. 자세한 내용은 아래에서 설명하겠다.
Injector
앵귤러에는 인젝터라는 것이 존재하는데 이 친구가 해당 컴포넌트, 디렉티브, 서비스의 생성자에 명시된 의존성의 타입을 체크하여 주입해준다. 인젝터는 인스턴스 저장소를 탐색하면서 컴포넌트의 생성자에 명시된 인스턴스가 존재하는지 체크한다. 인스턴스가 없으면 새로운 인스턴스를 생성하고 해당 인스턴스를 인스턴스 저장소에 보관한다.
의존성 인스턴스의 계층을 분리하는 방법은 다음과 같다.
@Injectable
에서providedIn
에root
또는@NgModule()
로 등록- 모듈의
providers
배열에 등록- 번들 사이즈 최적화를 위해서
providedIn
에 등록하는 걸 권장한다.
- 번들 사이즈 최적화를 위해서
- 컴포넌트, 디렉티브의
providers
배열에 등록
최상위에 생성되는 인젝터를 RootInjector
라고 부르고 필요에 의해 ModuleInjector
, ElementInjector
를 생성하여 의존성을 관리한다.
자바스크립트의 Prototype
과 매커니즘이 비슷하다고 생각이 든다. 현재 인젝터에서 찾을 수 없으면 부모 인젝터로 올라가면서 적절한 의존성을 발견한다. Prototype
에도 최상단이 존재하듯, 인젝터의 최상단은 NulInjector
이다. 앵귤러에서 제공하는 @Optinal()
데코레이터를 사용하지 않는 한 NulInjector
에 도달하면 오류로 간주한다.
의존성 토큰 결정
ElementInjector
-> ModuleInjector
-> RootInjector
-> PlatformModuleInjector
-> NulInjector
기본적인 의존성 토큰 결정 방식은 위와 같은 흐름인데, 앵귤러는 @Optinal()
데코레이터처럼 의존성 토큰 결정을 변경하는 것을 지원하고 있다.
@Optional()
@Optional()
데코레이터를 사용하면 서비스 프로바이더를 찾지 못했을 때 에러가 발생하는 대신 null
값을 주입한다.
constructor(@Optinal() private myService?: MyService) [}
@Self()
현재 계층의 컴포넌트/디렉티브의 ElementInjector
에서만 서비스 프로바이더를 찾는다.
constructor(@Self() private myService?: MyService) [}
@SkipSelf()
@SkipSelf()
는 @Self()
와 반대로 서비스 프로바이더를 찾을 때 현재 계층을 건너 뛰고 부모 계층의 ElementInjector
부터 탐색하도록 한다.
constructor(@SkipSelf() private myService?: MyService) [}
@Host()
@Host()
데코레이터를 사용하면 의존성으로 주입하는 객체의 프로바이더를 찾는 범위를 호스트 엘리먼트까지로 제한한다. 그 위쪽에 서비스 인스턴스가 있어도 의존성을 주입하지 않는다.
constructor(@Host() private myService?: MyService) [}
위와 같이 토큰 결정 방식을 변경할 때 해당 값이 null
이어도 괜찮다면 @Optional()
데코레이션을 함께 사용할 수 있다.
constructor(@Self() @Optional() private myService?: MyService) [}
constructor(@SkipSelf() @Optional() private myService?: MyService) [}
constructor(@Host() @Optional() private myService?: MyService) [}
Ghost