Angular(v21)에서 Effects와 InjectionContext
Source: Dev.to
The Injector: The “Source of Truth”
Injector는 서비스와 의존성 인스턴스를 보관하는 컨테이너입니다. Angular에서는 Injector 계층 구조(환경 Injector, 컴포넌트 Injector 등)가 존재합니다.
effect를 생성할 때, 그것은 Injector에 고정되어야 합니다. Injector는 effect가 필요로 할 수 있는 서비스들을 제공하고, 더 중요한 것은 effect의 수명을 결정합니다.
Injection Context: The “Where” and “When”
Injection Context는 inject() 함수가 사용 가능한 코드 실행 중의 특정 상태를 의미합니다. 객체(컴포넌트, 디렉티브 등)가 생성되는 기간이라고 생각하면 됩니다.
기본적으로 Injection Context는 다음에서 사용할 수 있습니다:
- 클래스의 생성자.
- 필드 초기화(예:
myService = inject(MyService)). - Provider의 팩토리 함수.
Why does an effect need it?
생성자에서 effect(() => { … })를 호출하면, Angular는 현재 Injection Context를 암묵적으로 찾아 DestroyRef를 “주입”합니다. 이를 통해 effect를 언제 “종료”해야 하는지 정확히 파악하여 메모리 누수를 방지합니다.
The inject() Function: The Modern Fetcher
inject() 함수는 현재 Injection Context에서 의존성을 프로그래밍적으로 가져오는 방법입니다.
일반 메서드(예: 버튼 클릭) 내부에서 effect를 만들려고 하면, 해당 메서드에는 Injection Context가 없기 때문에 실패합니다.
// ✅ WORKS: In the constructor / class init
count = signal(0);
myEffect = effect(() => console.log(this.count()));
// ❌ FAILS: Outside injection context
updateData() {
effect(() => { /* … */ }); // Error: inject() must be called from an injection context
}
Capturing the injector manually
export class MyComponent {
private injector = inject(Injector); // Capture the injector in the constructor
startManualEffect() {
// Pass the captured injector to the effect
effect(() => {
console.log('Manual effect running!');
}, { injector: this.injector });
}
}
DestroyRef: The “Clean‑up Crew”
DestroyRef는 OnDestroy 라이프사이클 훅의 현대적인 대체물입니다. 클래스 정의에만 국한되지 않고 코드 어디에서든 정리 로직을 등록할 수 있게 해줍니다.
effect가 생성될 때, 내부적으로 해당 컨텍스트의 DestroyRef에 구독합니다. 그 effect를 보유한 컴포넌트나 서비스가 파괴되면 DestroyRef가 발동하고 effect가 자동으로 중지됩니다.
Manual cleanup with DestroyRef
const dr = inject(DestroyRef);
const sub = mySignal.subscribe(...);
dr.onDestroy(() => {
console.log('Cleaning up resources!');
sub.unsubscribe();
});
The runInInjectionContext Helper
때때로 Injector가 있지만 매 함수에 옵션으로 전달하고 싶지 않을 때가 있습니다. 컨텍스트를 “강제”할 수 있습니다:
import { runInInjectionContext } from '@angular/core';
runInInjectionContext(this.injector, () => {
// Anything called here now has access to inject()
effect(() => console.log('I have context!'));
});
Code Playground
Key Points to Remember
- Effects는 Injector에 연결됩니다.
- Injectors는 DestroyRef를 사용해 정리합니다.
inject()는 Injection Context가 활성화된 경우에만 작동합니다.- 생성자 외부에서 effect를 만들 경우, Injector를 수동으로 제공해야 합니다.
Happy coding!