2021. 7. 5. 22:29ㆍFrontend/Angular
** 초보 개발자로 글에 수정해야 할 부분이 있을 수 있습니다. 정정해야 할 부분은 댓글로 소통 부탁드립니다!
정말 코딩의 세계는 무궁무진한 것 같습니다.
provider는 당연히 module에서만 import 하는 것이라고 생각했는데
component 별로 import가 가능하더군요!
(lazy loading의 진수를 보여주는! 👍🏻)
구글링 하다가 좋은 영문글을 발견하여 번역해보려 합니다!
STEP 01 _ 준비하기
먼저, 각 provider를 비교하기 위한 예제 코드(5개 파일)를 이해 해봅시다.
- SimpleService
- ChildComponent
- ParentComponent
- AppComponent
- AppModule
1. SimpleService
class SimpleService {
value: string;
}
2. Child Component
@Component({
selector: 'child',
template: `
<div class="child">
<p>Child</p>
{{ service.value }} // (1)
</div>
})
class ChildComponent {
constructor(private service: SimpleService) { } // (2)
}
(1) : {{ }} 를 사용해 string interpolation from simple service
(2) : 생성자에 service 를 주입
3. Parent Component
@Component({
selector: 'parent',
template: `
<div class="parent">
<p>Parent</p>
<form novalidate>
<div class="form-group">
<input type="text"
class="form-control"
name="value"
[(ngModel)]="service.value"> // (1)
</div>
</form>
<child></child> // (2)
</div>
`
})
class ParentComponent {
constructor(private service: SimpleService) { } // (3)
}
(1) : two-way binding to bind the value of SimpleService
(2) : rendering ChildComponent
(3) : 생성자에 service 를 주입
4. AppComponent
@Component({
selector: 'app',
template: `
<div class="row">
<div class="col-xs-6">
<parent></parent>
</div>
<div class="col-xs-6">
<parent></parent>
</div>
</div>
`
})
class AppComponent {
}
5. AppModule
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, ParentComponent, ChildComponent ],
bootstrap: [ AppComponent ]
})
class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
AppComponent 의 모습은 다음 그림과 같이 출력됩니다.
STEP 02 _ NgModule.providers
가장 보편적으로 사용하는 방법이죠! (저는 그랬습니다.. ㅎㅎ)
위에서 생성한 SimpleService 를 module 의 providers 로 import 하는 방식입니다.
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, ParentComponent, ChildComponent ],
bootstrap: [ AppComponent ],
providers: [ SimpleService ] // (1)
})
class AppModule { }
(1) : AppModule의 providers 에 SimpleService를 import
이 경우 service가 root NgModule 에 주입되었으므로 결국 root injector에 주입된 것입니다.
그래서 SimpleService로 들어오는 모든 요청은 하나의 root injector로 전달 됩니다.
이 경우 하나의 injector(root injector 내의 SimpleService) 만 존재하기 때문에
각 component 에서 SimpleService instance를 요청할 때마다(= constructor 에서 주입 했었죠!)
같은 instance가 반환 됩니다.
! Important
같은 injector 에서 같은 token을 요청하면, 같은 instance가 반환됩니다.
(하나의 input에만 입력했는데 동시에 값이 update 됩니다. wow 🥲 )
Tip
전체 application 에서 하나의 service instance 를 공유하고 싶다면, NgModule 에 service를 import 하면 됩니다.
STEP 03 _ Component.providers
그렇다면, ParentComponent 의 providers에 SimpleService 를 import 하는 경우에는 어떤 일이 일어나는지 확인해봅시다!
@Component({
selector: 'parent',
template: `...`,
providers: [ SimpleService ]
})
class ParentComponent {
constructor(private service: SimpleService) { }
}
이 경우에는 하나의 ParentComponent 가 하나의 SimpleService를 가지게 됩니다. 요렇게 :
실제 출력 결과 또한 앞의 경우와 차이가 있습니다.
input 이 각각 동작하게 되죠!
각 ParentComponet가 자신 고유의 SimpleService를 가지게 되므로 state 이 global 하게 공유되지 않고
ParentComponent 와 ChildComponent 간에서만 공유되는 것을 확인할 수 있습니다.
! Important
다른 injector 에서 같은 token 을 요청할 때, 다른 instance가 반환됩니다.
ParentComponent 에서 SimpleService를 import 하면, child injector를 생성합니다.
그리고 ParentComponent 의 생성자에서 SimpleService를 주입하면,
child injector로 부터 SimpleService instance를 생성합니다.
Tip
각 component 마다 하나의 service instance 를 생성하여 child component 와 공유하고 싶다면,
component decorator 의 providers property 에 service를 import 하세요.
STEP 03 _ Component.viewProviders
ParentComponent 의 viewProviders property에 SimpleService 를 import 하면, 전과 동일하게 동작합니다.
변화를 주기 위해 ng-content 를 사용해 봅시다.
그러려면 앞선 예제 코드를 변경해야 합니다.
AppComponet의 html 을 다음과 같이 변경합니다.
<div class="row">
<div class="col-xs-6">
<parent><child></child></parent> // (*) change
</div>
<div class="col-xs-6">
<parent><child></child></parent> // (*) change
</div>
</div>
Parent Component 도 다음과 같이 변경합니다.
<div class="parent">
<p>Parent</p>
<form novalidate>
<div class="form-group">
<input type="text"
class="form-control"
name="value"
[(ngModel)]="service.value">
</div>
</form>
<ng-content></ng-content> // (1)
</div>
(1) : 전에 hard coding 되어 있던 ChildComponent 를 content projection 으로 대체했습니다.
동일하게 child component 가 출력되지만, view child 가 아닌 content child 로 인식됩니다.
ParentComponet의 Providers 를 ViewProviders 로 변경해봅시다.
@Component({
selector: 'parent',
template: `...`,
viewProviders: [ SimpleService ]
})
class ParentComponent {
constructor(private service: SimpleService) { }
}
그렇다면, 이 경우는 어떻게 화면에서 동작할까요?
ParentComponent 에서 input에 입력하더라도
ChildComponent 에서 자동으로 갱신되지 않습니다.
왜냐하면 ! viewProviders 를 사용하면, 현재 컴포넌트 와 view children 에서만 사용되는 injector를 생성합니다.
현재 child component 와 같이 content child 의 경우에는 NgModule 의 injector 를 사용합니다.
Tip
각 component 마다 하나의 service instance 를 생성하여 view child component 와 공유하고 싶다면,
( content child component 는 제외하고)
component decorator 의 viewProviders property 에 service를 import 하세요.
[References]
'Frontend > Angular' 카테고리의 다른 글
[Angular] HostBinding VS. HostListener (0) | 2021.07.05 |
---|---|
[Angular] ngClass 로 클래스 추가하기 (0) | 2021.07.01 |
[Angular] NGRX 적용 - (1) ngrx 동작 과정 이해하기 (0) | 2021.06.30 |