diff --git a/public/docs/ts/latest/cookbook/component-communication.jade b/public/docs/ts/latest/cookbook/component-communication.jade index ab87d707..d8588d59 100644 --- a/public/docs/ts/latest/cookbook/component-communication.jade +++ b/public/docs/ts/latest/cookbook/component-communication.jade @@ -2,206 +2,327 @@ include ../_util-fns :marked + 이 쿡북은 두개 이상의 컴포넌트가 정보를 공유하는 주된 시나리오들에 대한 레시피들로 이루어져 있습니다. + This cookbook contains recipes for common component communication scenarios in which two or more components share information. // .l-sub-section :marked + 컴포넌트 간 통신에 대한 각각의 기본적인 개념에 대해 알고 싶다면, [컴포넌트 간 통신]() 문서에서 자세한 설명과 예시들을 찾아볼 수 있습니다. + For an in-depth look at each fundamental concepts in component communication, we can find detailed description and samples in the [Component Communication]() document. :marked + ## 목차 + ## Table of contents - + + [인풋 바인딩으로 부모 컴포넌트에서 자식 컴포넌트로 데이터 보내기](#parent-to-child) + [Pass data from parent to child with input binding](#parent-to-child) + [setter로 인풋 속성 변경 잡아내기](#parent-to-child-setter) + [Intercept input property changes with a setter](#parent-to-child-setter) + [*ngOnChanges*로 바뀌는 인풋 값을 잡아내기](#parent-to-child-on-changes) + [Intercept input property changes with *ngOnChanges*](#parent-to-child-on-changes) + [부모 컴포넌트가 자식 컴포넌트 event를 받아올 때](#child-to-parent) + [Parent listens for child event](#child-to-parent) - + + [*지역 변수*로 부모 컴포넌트와 자식 컴포넌트 간 상호작용하기](#parent-to-child-local-var) + [Parent interacts with child via a *local variable*](#parent-to-child-local-var) - + + [ViewChild를 이용해 부모 컴포넌트에서 자식 컴포넌트 불러오기](#parent-to-view-child) + [Parent calls a *ViewChild*](#parent-to-view-child) + [service를 통한 부모와 자식 컴포넌트 간 통신하기](#bidirectional-service) + [Parent and children communicate via a service](#bidirectional-service) :marked + **예시는 여기를 참고하세요**. + **See the **. .l-main-section :marked + + ## 인풋 바인딩으로 부모 컴포넌트에서 자식 컴포넌트로 데이터 보내기 + ## Pass data from parent to child with input binding - - `HeroChildComponent` has two ***input properties***, + + `HeroChildComponent`는 두 개의 ***인풋 속성들***을 지니고 있고, + [@Input decorations](../guide/template-syntax.html#inputs-outputs)들로 꾸며져 있지요. + + `HeroChildComponent` has two ***input properties***, typically adorned with [@Input decorations](../guide/template-syntax.html#inputs-outputs). - + +makeExample('cb-component-communication/ts/src/app/hero-child.component.ts') :marked + 두 번째 `@Input`, 즉 인풋 속성은 자식컴포넌트의 속성 이름인 `masterName`를 `'master'`로 줄여줍니다. + The second `@Input` aliases the child component property name `masterName` as `'master'`. - - The `HeroParentComponent` nests the child `HeroChildComponent` inside an `*ngFor` repeater, + + 그래서 그 부모인 `HeroParentComponent`는 그의 자식 컴포넌트 `HeroChildComponent`를 `*ngFor` 반복자 안에 넣고, + 부모의 `master` 스트링 타입 속성을 자식 컴포넌트의 `master` 바로가기에 묶고 + 매 반복 때 마다 쓰이는 `hero` 인스턴스를 자식 컴포넌트의 `hero` 속성에 묶습니다. + + + The `HeroParentComponent` nests the child `HeroChildComponent` inside an `*ngFor` repeater, binding its `master` string property to the child's `master` alias and each iteration's `hero` instance to the child's `hero` property. +makeExample('cb-component-communication/ts/src/app/hero-parent.component.ts') :marked + 작동하는 웹 앱은 3명의 영웅들을 보여줍니다. + The running application displays three heroes: - + figure.image-display img(src="/resources/images/cookbooks/component-communication/parent-to-child.png" alt="Parent-to-child") - + :marked + ### 테스트 해봅시다 + ### Test it - + + 자식 컴포넌트들을 위한 E2E test가 실행되었고 예상되로 결과가 보여집니다: + E2E test that all children were instantiated and displayed as expected: - + +makeExample('cb-component-communication/e2e-spec.ts', 'parent-to-child') :marked + [맨 위로](#top) + [Back to top](#top) .l-main-section :marked + ## setter로 인풋 속성 변경 잡아내기 + ## Intercept input property changes with a setter + 인풋 속성 setter를 써서 부모로부터 받은 값을 잡아내고 그에 대해 반응시켜보세요. + Use an input property setter to intercept and act upon a value from the parent. - - The setter of the `name` input property in the child `NameChildComponent` - trims the whitespace from a name and replaces an empty value with default text. - + + 자식 컴포넌트 `NameChildComponent`에 있는 `name` 인풋 속성에 대한 setter는 + 이름에 있는 띄어쓰기를 다듬고 기존에 정해진 텍스트로 비어진 값을 채워줍니다. + + The setter of the `name` input property in the child `NameChildComponent` + trims the whitespace from a name and replaces an empty value with default text. + +makeExample('cb-component-communication/ts/src/app/name-child.component.ts') :marked + 여기 `NameParentComponent`가 띄어쓰기만을 포함한 다양한 name 값에 대해 어떻게 대처하는지 보여줍니다. + Here's the `NameParentComponent` demonstrating name variations including a name with all spaces: +makeExample('cb-component-communication/ts/src/app/name-parent.component.ts') figure.image-display img(src="/resources/images/cookbooks/component-communication/setter.png" alt="Parent-to-child-setter") - + :marked + ### 테스트 해봅시다 + ### Test it + 비어있거나 안 비어있는 name값을 이용한 인풋 속성 setter 에 대한 E2E 테스트들: + E2E tests of input property setter with empty and non-empty names: - + +makeExample('cb-component-communication/e2e-spec.ts', 'parent-to-child-setter') :marked + [맨 위로](#top) + [Back to top](#top) .l-main-section :marked + ## *ngOnChanges* 로 인풋 속성 변경을 잡아내기 + ## Intercept input property changes with *ngOnChanges* + 컴포넌트의 생명주기 훅 인터페이스인 `OnChanges`의 `ngOnChanges` 메소드로 인풋 속성 변경을 감지하고 이에 대해 반응하게 해봅시다. + Detect and act upon changes to input property values with the `ngOnChanges` method of the `OnChanges` lifecycle hook interface. .l-sub-section :marked + 만약 여러개의 상호작용하는 인풋 속성을 지켜봐야 한다면 속성 setter 대신 이것을 추천합니다. + May prefer this approach to the property setter when watching multiple, interacting input properties. - + + [LifeCycle Hooks](../guide/lifecycle-hooks.html) 챕터에 있는 `ngOnChanges` 에 대해 알아보세요. + Learn about `ngOnChanges` in the [LifeCycle Hooks](../guide/lifecycle-hooks.html) chapter. :marked + `VersionChildComponent`는 `major` 와 `minor` 인풋 속성을 인지하고 값들의 변경에 따라 log message를 작성합니다. + This `VersionChildComponent` detects changes to the `major` and `minor` input properties and composes a log message reporting these changes: - + +makeExample('cb-component-communication/ts/src/app/version-child.component.ts') :marked + `VersionParentComponent`는 `minor` 와 `major` 값들을 가지고 있고 값들을 변경하는 메소드를 이어주는 버튼을 보여줍니다. + The `VersionParentComponent` supplies the `minor` and `major` values and binds buttons to methods that change them. - + +makeExample('cb-component-communication/ts/src/app/version-parent.component.ts') :marked + 버튼을 눌렀을 때의 과정은 다음과 같습니다: + Here's the output of a button-pushing sequence: - + figure.image-display img(src="/resources/images/cookbooks/component-communication/parent-to-child-on-changes.gif" alt="Parent-to-child-onchanges") - + :marked + ### 테스트 해봅시다 + ### Test it - - Test that ***both*** input properties are set initially and that button clicks trigger + + 양 속성 모두 초기값이 세팅이 되어있고 버튼 클릭이 예정된 `ngOnChanges` 요청과 값을 일으키는지에 대한 테스트: + + Test that ***both*** input properties are set initially and that button clicks trigger the expected `ngOnChanges` calls and values: - + +makeExample('cb-component-communication/e2e-spec.ts', 'parent-to-child-onchanges') :marked + [맨 위로](#top) + [Back to top](#top) .l-main-section :marked + ## 부모 컴포넌트가 자식 컴포넌트 event를 받아올 때 + ## Parent listens for child event - The child component exposes an `EventEmitter` property with which it `emits`events when something happens. + 자식 컴포넌트는 뭔가 일어 났을 때 event들을 `보여주는` `EventEmitter` 속성을 (부모 컴포넌트에게)드러냅니다. + + The child component exposes an `EventEmitter` property with which it `emits`events when something happens. The parent binds to that event property and reacts to those events. - - The child's `EventEmitter` property is an ***output property***, + + 자식 컴포넌트의 `EventEmitter` 속성은 ***출력 속성***에 속하고, 전형적으로 아래 `VoterComponent`에서 보여지는 것처럼 꾸며집니다: + + The child's `EventEmitter` property is an ***output property***, typically adorned with an [@Output decoration](../guide/template-syntax.html#inputs-outputs) as seen in this `VoterComponent`: - + +makeExample('cb-component-communication/ts/src/app/voter.component.ts') :marked + 버튼을 클릭하면 (불리언 *페이로드*)인 `true` 나 `false` 값을 보내게 됩니다. + Clicking a button triggers emission of a `true` or `false` (the boolean *payload*). - + + 부모 컴포넌트인 `VoteTakerComponent`는 (`onVoted`) 이벤트 핸들러를 묶고 자식 이벤트 페이로드인 (`$event`)에 반응하여 + 카운터를 갱신해줍니다. + The parent `VoteTakerComponent` binds an event handler (`onVoted`) that responds to the child event payload (`$event`) and updates a counter. - + +makeExample('cb-component-communication/ts/src/app/votetaker.component.ts') :marked - The framework passes the event argument — represented by `$event` — to the handler method, + 이 프레임워크는 이벤트 변수— 즉 `$event`를— 핸들러 메소드로 넘겨주고, 메소드는 그것을 처리합니다. + + The framework passes the event argument — represented by `$event` — to the handler method, and the method processes it: - + figure.image-display img(src="/resources/images/cookbooks/component-communication/child-to-parent.gif" alt="Child-to-parent") - + :marked + ### 테스트 해봅시다 + ### Test it - + + *Agree* 와 *Disagree* 버튼들을 클릭하면 적절한 카운터들을 갱신시키는지 테스트: + Test that clicking the *Agree* and *Disagree* buttons update the appropriate counters: - + +makeExample('cb-component-communication/e2e-spec.ts', 'child-to-parent') :marked + [맨 위로](#top) + [Back to top](#top) .l-main-section#parent-to-child-local-var :marked + ## *지역 변수*로 부모 컴포넌트와 자식 컴포넌트 간 상호작용하기 + ## Parent interacts with child via *local variable* - + + 부모 컴포넌트는 데이터 바인딩으로 자식 컴포넌트의 속성을 읽어오거나 + 자식 컴포넌트의 메소드들을 불러낼 수 없습니다. 하지만 자식 컴포넌트 속성에 대한 template reference 변수 + 를 만들어 낸 다음 그 변수를 *부모 컴포넌트 template 안에* 언급할 수 있습니다. + 마치 아래에 있는 예시처럼 말이죠. + A parent component cannot use data binding to read child properties - or invoke child methods. We can do both + or invoke child methods. We can do both by creating a template reference variable for the child element and then reference that variable *within the parent template* as seen in the following example. + 여기서 우리는 0까지 아래로 세고 로켓을 발사하는 자식 컴포넌트 `CountdownTimerComponent`가 있군요. + 카운트다운을 하는 시계를 제어하는 `start` 와 `stop`를 메소드를 가지고 있고 카운트다운 상태 메시지를 자신의 template에 + 보여줍니다. + We have a child `CountdownTimerComponent` that repeatedly counts down to zero and launches a rocket. It has `start` and `stop` methods that control the clock and it displays a countdown status message in its own template. +makeExample('cb-component-communication/ts/src/app/countdown-timer.component.ts') :marked + 그 '타이머 컴포넌트'(CountdownTimerComponent)를 보여주는 `CountdownLocalVarParentComponent`를 보도록 하죠. + Let's see the `CountdownLocalVarParentComponent` that hosts the timer component. - + +makeExample('cb-component-communication/ts/src/app/countdown-parent.component.ts', 'lv') :marked - The parent component cannot data bind to the child's + 부모 컴포넌트는 자식 컴포넌트의 `start` 와 `stop` 메소드들에 대해 데이터 바인딩을 할 수 없으며 + `seconds` 속성에 대해서도 할 수 없습니다. + + The parent component cannot data bind to the child's `start` and `stop` methods nor to its `seconds` property. - + + 이 때 자식 컴포넌트를 가리키는 지역변수 (`#timer`)를 (``)라는 태그 안에 지정해줍니다. + 이는 자식 컴포넌트를 가리키는 레퍼런스가 생기고 자식 컴포넌트의 *모든 속성과 메소드들*을 부모 컴포넌트 template 안에서 액세스할 수 있습니다. + We can place a local variable (`#timer`) on the tag (``) representing the child component. That gives us a reference to the child component itself and the ability to access *any of its properties or methods* from within the parent template. - + + 이번 예시에서는, 부모 컴포넌트의 버튼을 자식 컴포넌트의 `start` 와 `stop` 버튼에 연결시키고 + 보간을 이용하여 자식 컴포넌트의 `seconds` 속성을 보여줍니다. + In this example, we wire parent buttons to the child's `start` and `stop` and use interpolation to display the child's `seconds` property. - + + 아래 예시에서는 부모와 자식 컴포넌트가 어떻게 같이 움직이는지 보여줍니다. + Here we see the parent and child working together. figure.image-display @@ -209,123 +330,214 @@ figure.image-display a(id="countdown-tests") :marked + ### 테스트 해봅시다 + ### Test it - + + 부모 컴포넌트에서 보여주는 숫자 template이 자식 컴포넌트의 상태 메세지와 일치하는지 테스트해봅시다. + 또한 *Stop* 버튼을 클릭함으로서 카운트다운 타이머를 멈출 수 있는지도 테스트해봅시다: + Test that the seconds displayed in the parent template match the seconds displayed in the child's status message. Test also that clicking the *Stop* button pauses the countdown timer: - + +makeExample('cb-component-communication/e2e-spec.ts', 'countdown-timer-tests') :marked + [맨 위로](#top) + [Back to top](#top) - + .l-main-section :marked + ## *ViewChild*를 이용해 부모 컴포넌트에서 자식 컴포넌트 불러오기 + ## Parent calls a *ViewChild* - - The *local variable* approach is simple and easy. But it is limited because + + *지역 변수*를 이용한 방법은 간단하고 쉽습니다. 하지만 이 방법은 제한적인데요. 왜냐하면 부모-자식 간의 + 연결이 모두 다 부모 컴포넌트 안의 template 안에서 이루어져야 하기 때문입니다. + 부모 컴포넌트 그 *자신*은 자식 컴포넌트에 액세스할 수 없는 것이지요. + + The *local variable* approach is simple and easy. But it is limited because the parent-child wiring must be done entirely within the parent template. The parent component *itself* has no access to the child. - + + 만약 부모 컴포넌트 *클래스*의 인스턴스가 자식 컴포넌트의 값을 읽고 써야 한다거나 자식 컴포넌트의 메소드를 불러와야 한다면 + *지역 변수* 를 이용한 방법을 사용할 수 없습니다. + We can't use the *local variable* technique if an instance of the parent component *class* must read or write child component values or must call child component methods. - - When the parent component *class* requires that kind of access, + + 부모 컴포넌트 *클래스*가 이러한 액세스를 필요로 한다면, + 부모 컴포넌트에 자식 컴포넌트를 *ViewChild*로 ***주입*** 시키세요. + + When the parent component *class* requires that kind of access, we ***inject*** the child component into the parent as a *ViewChild*. - - We'll illustrate this technique with the same [Countdown Timer](#countdown-timer-example) example. - We won't change its appearance or behavior. + + 우리는 이 기술을 같은 예시인 [카운트다운 타이머](#countdown-timer-example)로 구현해볼 것입니다. + view이나 행동을 바꾸지 않고서 말이죠. + 자식 컴포넌트 [CountdownTimerComponent](#countdown-timer-example)도 마찬가지입니다. + + We'll illustrate this technique with the same [Countdown Timer](#countdown-timer-example) example. + We won't change its appearance or behavior. The child [CountdownTimerComponent](#countdown-timer-example) is the same as well. .l-sub-section :marked + 우리는 설명을 위해 오직 *지역변수*에서 *ViewChild*를 이용한 기술로 바꾸기만 할 것입니다. + We are switching from the *local variable* to the *ViewChild* technique solely for the purpose of demonstration. :marked + 여기 부모인 `CountdownViewChildParentComponent`가 있습니다: + Here is the parent, `CountdownViewChildParentComponent`: +makeExample('cb-component-communication/ts/src/app/countdown-parent.component.ts', 'vc') :marked + 자식 컴포넌트의 view를 부모 컴포넌트 *클래스*에 구현하기 위해서는 조금 더 많은 노력이 필요합니다. + It takes a bit more work to get the child view into the parent component *class*. - + + `ViewChild` 데코레이터와 and the `AfterViewInit` 생명주기 훅 에 대한 레퍼런스를 들여옵니다. + We import references to the `ViewChild` decorator and the `AfterViewInit` lifecycle hook. - + + 자식 컴포넌트인 `CountdownTimerComponent`를 `@ViewChild` property decoration으로 private 속성인 + `timerComponent`에 주입시켜 줍니다. + We inject the child `CountdownTimerComponent` into the private `timerComponent` property via the `@ViewChild` property decoration. - - The `#timer` local variable is gone from the component metadata. + + `#timer` 지역변수는 메타 데이터(태그)에서 사라졌습니다. + 대신 버튼들을 부모 컴포넌트의 `start` 와 `stop` 메소드에 연결시키고 + 보간을 이용해 초가 지나가는 것을 부모 컴포넌트의 `seconds` 메소드로 보여줍니다. + + The `#timer` local variable is gone from the component metadata. Instead we bind the buttons to the parent component's own `start` and `stop` methods and present the ticking seconds in an interpolation around the parent component's `seconds` method. - + + 이 메소드들은 주입된 타이머 컴포넌트에 직접 액세스 합니다. + These methods access the injected timer component directly. - + + `ngAfterViewInit` 생명주기 훅은 매우 중요한 방법입니다. + 이를 이용하면 타이머 컴포넌트는 앵귤러가 부모 컴포넌트의 view를 보여준 *후* 까지는 접근이 불가능합니다. + 그래서 초기값으로 `0`을 보여줍니다. + The `ngAfterViewInit` lifecycle hook is an important wrinkle. The timer component isn't available until *after* Angular displays the parent view. So we display `0` seconds initially. - + + 그 후엔 앵귤러는 `ngAfterViewInit` 생명주기 훅을 부모 컴포넌트 view의 모습을 카운트다운 초로 갱신하기 에 + *너무 늦을 때* 불러냅니다. + 앵귤러의 방향이 정해지지 않은 데이터 흐름 규정은 부모 컴포넌트 view를 같은 주기에서 갱신시키는 것을 미리 막게 해줍니다. + 그래서 초를 보여주기 전에 *한 번 기다려야* 합니다. + Then Angular calls the `ngAfterViewInit` lifecycle hook at which time it is *too late* to update the parent view's display of the countdown seconds. Angular's unidirectional data flow rule prevents us from updating the parent view's in the same cycle. We have to *wait one turn* before we can display the seconds. - - We use `setTimeout` to wait one tick and then revise the `seconds` method so + + `setTimeout`을 이용해 1초가 지나가는 것을 기다리고 `seconds` 메소드가 + 타이머 컴포넌트로부터 나중에 들어올 값을 받을 수 있게 수정해줘야 힙니다. + + We use `setTimeout` to wait one tick and then revise the `seconds` method so that it takes future values from the timer component. + ### 테스트 해봅시다 + ### Test it + + [전에 했던 카운트다운 타이머 테스트들](#countdown-tests)를 사용하십시오. + Use [the same countdown timer tests](#countdown-tests) as before. :marked + [맨 위로](#top) + [Back to top](#top) - + .l-main-section :marked + ## service를 통한 부모와 자식 컴포넌트 간 통신하기 + ## Parent and children communicate via a service + 하나의 부모 컴포넌트와 그것의 자식 컴포넌트들은 *가족 내에서* 쌍방향 통신이 가능하게 하는 인터페이스를 가진 service를 공유한다고 해봅시다. + A parent component and its children share a service whose interface enables bi-directional communication *within the family*. - The scope of the service instance is the parent component and its children. + 이 service 인스턴스의 범위는 부모 컴포넌트와 그 자식들입니다. + 이 컴포넌트의 가족구도를 벗어난 컴포넌트들은 그들이 가지고 있는 service나 통신에 접근할 수 없습니다. + + The scope of the service instance is the parent component and its children. Components outside this component subtree have no access to the service or their communications. - + + 이 `MissionService`는 `MissionControlComponent`를 여러 개의 `AstronautComponent` 자식 컴포넌트들에 연결시킵니다. + This `MissionService` connects the `MissionControlComponent` to multiple `AstronautComponent` children. +makeExample('cb-component-communication/ts/src/app/mission.service.ts') :marked + `MissionControlComponent` 는 자식 컴포넌트와 공유하는 service 인스턴스를 제공하고(`providers` 메타데이터 배열을 통해서) + 그 인스턴스를 자신의 건설자에 주입합니다. + The `MissionControlComponent` both provides the instance of the service that it shares with its children (through the `providers` metadata array) and injects that instance into itself through its constructor: - + +makeExample('cb-component-communication/ts/src/app/missioncontrol.component.ts') :marked + `AstronautComponent` 또한 service를 건설자에 주입시킵니다. + `AstronautComponent`는 `MissionControlComponent`의 자식 컴포넌트이므로 부모 컴포넌트의 서비스 인스턴스를 물려받습니다. + The `AstronautComponent` also injects the service in its constructor. Each `AstronautComponent` is a child of the `MissionControlComponent` and therefore receives its parent's service instance: - + +makeExample('cb-component-communication/ts/src/app/astronaut.component.ts') .l-sub-section :marked + 위 예시가 `subscription`을 잡을 수 있다는 것과 `AstronautComponent`가 파괴되었을 때 구독을 취소할 수 있다는 것에 주목하십시오. + 이것은 메모리 누수를 막기 위한 과정입니다. `AstronautComponent` 의 생명 주기는 앱의 생명주기와 같기 때문에 이 앱에서 실제로 문제가 될 만한 일은 없습니다. + 이것은 그러나 복잡한 구조의 앱에서는 항상 참이 *아닙니다*. + Notice that we capture the `subscription` and unsubscribe when the `AstronautComponent` is destroyed. This is a memory-leak guard step. There is no actual risk in this app because the lifetime of a `AstronautComponent` is the same as the lifetime of the app itself. That *would not* always be true in a more complex application. - + + `MissionControlComponent`에는 이러한 예방책을 적용할 수 없는데, 이는 부모 컴포넌트로서 + `MissionService`의 생명주기를 제어할 수 있기 때문입니다. + We do not add this guard to the `MissionControlComponent` because, as the parent, it controls the lifetime of the `MissionService`. :marked + *History* 기록은 service에 의해 만들어진 부모인 `MissionControlComponent`와 자식인 `AstronautComponent` + 사이의 양방향 메세지들을 보여줍니다. + The *History* log demonstrates that messages travel in both directions between the parent `MissionControlComponent` and the `AstronautComponent` children, facilitated by the service: - + figure.image-display img(src="/resources/images/cookbooks/component-communication/bidirectional-service.gif" alt="bidirectional-service") - + :marked + ### 테스트 해봅시다 + ### Test it - + + 부모 컴포넌트 `MissionControlComponent`와 자식 컴포넌트 `AstronautComponent`의 클릭 버튼들을 둘 다 테스트 해보고 + 예상한 대로 *History*가 기록되어 있는지 확인: + Tests click buttons of both the parent `MissionControlComponent` and the `AstronautComponent` children and verify that the *History* meets expectations: - + +makeExample('cb-component-communication/e2e-spec.ts', 'bidirectional-service') :marked + [맨 위로](#top) + [Back to top](#top)