MASTER/DETAIL

Build a master/detail page with a list of heroes.

In this page, you'll expand the Tour of Heroes app to display a list of heroes, and allow users to select a hero and display the hero's details.


영웅들의 리스트와 함께 마스터/디테일 페이지를 만드세요.

이번 페이지에서 여러분은 영웅들의 리스트를 보여주고 사용자들이 영웅을 선택하고 영웅의 디테일을 볼수 있게 앱을 확장할것입니다.


Where you left off

당신이 도착한곳


Before you continue with this page of the Tour of Heroes, verify that you have the following structure after The Hero Editor page. If your structure doesn't match, go back to that page to figure out what you missed.


여러분이 Tour of Heroes의 이 페이지를 시작하기 전에 다음 구조를 가지고 있는지 확인 하세요. 만약 당신의 구조가 다음 구조와 맞지 않다면 뒤로 돌아가서 무엇을 놓쳤는지 살펴보세요.


angular-tour-of-heroes

src

app

app.component.ts

app.module.ts

main.ts

index.html

styles.css

systemjs.config.js

tsconfig.json

node_modules ...

package.json


Keep the app transpiling and running

앱을 계속 transcompile하고 실행된 채로 두세요.


Enter the following command in the terminal window:

npm start

This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes. The command simultaneously launches the app in a browser and refreshes the browser when the code changes.


터미널에 다음 명령어를 실행 합시다:

npm start

이 명령어는 Typescript compiler를 “watch mode”에 둡니다. 코드가 바뀌면 자동으로 재컴파일됩니다.


You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.


여러분은 Tour of Heroes를 정지시켜 재컴파일시키거나 브라우저를 새로고침하지 않고 계속해서 만들 수 있게 되었습니다.


Displaying heroes

영웅 보여주기


To display a list of heroes, you'll add heroes to the view's template.


영웅의 리스트를 보여주기 위해서, 여러분은 영웅들을 뷰의 템플릿에 추가할 것입니다.


Create heroes

영웅 만드세요


Create an array of ten heroes.


영웅을 배열로 10명 만드세요.


srcappapp.component.ts (hero array)

const HEROES: Hero[] = [
  { id: 11, name: 'Mr. Nice' },
  { id: 12, name: 'Narco' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];

The HEROES array is of type Hero, the class defined in the previous page. Eventually this app will fetch the list of heroes from a web service, but for now you can display mock heroes.


HEROES배열은 Hero클래스 타입이 이전 페이지에 정의된 것 을 말한다. 결국 이 앱은 웹서비스로부터 영웅의 리스트를 가져올 것 이지만, 지금 여러분은 가짜의 영웅들을 보여줄 수 있다.


Expose heroes

영웅들을 노출시켜라


Create a public property in AppComponent that exposes the heroes for binding.


AppComponent에 영웅들을 바인딩하기 위해 노출하는 public 속성을 만들어라.


app.component.ts (hero array property)

heroes = HEROES;

The heroes type isn't defined because TypeScript infers it from the HEROES array.


heroes타입은 타입스크립트가 HEROES배열로 부터 추론하기 때문에 정의되지 않았다.


The hero data is separated from the class implementation because ultimately the hero names will come from a data service.


영웅 데이터는 클래스 이식 과정에서 분리되었다. 왜냐하면 결국 영웅의 이름이 데이터 서비스로부터 올것이기 때문이다.


Display hero names in a template

템플릿에 영웅이름 보여주기


To display the hero names in an unordered list, insert the following chunk of HTML below the title and above the hero details.


순서가 없는 이스트에서 영웅의 이름을 보여주기 위해서는, 다음 HTML을 제목 아래, 영웅의 세부사항 위에 삽입하세요.


app.component.ts (heroes template)

<h2>My Heroes</h2>
<ul class="heroes">
  <li>
    <!-- each hero goes here -->
  </li>
</ul>

Now you can fill the template with hero names.


이제 여러분은 영웅의 이름으로 템플릿을 채워 넣을 수 있다.

List heroes with ngFor

영웅들을 ngFor로 나열하기


The goal is to bind the array of heroes in the component to the template, iterate over them, and display them individually.


목표는 component에 있는 영웅의 배열을 템플릿으로 바인드하고, 반복하고, 그들을 각각 보여주는 것이다.


Modify the <li>tag by adding the built-in directive *ngFor.


<li> tag를 *ngFor 지시어를 추가하여 수정하여라. 


app.component.ts (ngFor)

`<li *ngFor="let hero of heroes">`


The (*) prefix to ngFor is a critical part of this syntax. It indicates that the <li>element and its children constitute a master template.


ngFor 앞에 붙는 (*)접두어는 이 문법의 핵심부분이다. 이는 <li>요소와 그 자식들이 마스터 템플릿을 구성한다는것을 보여준다.


The ngFor directive iterates over the component's heroes array and renders an instance of this template for each hero in that array.


ngFor 지시어는 component의 영웅 배열을 반복하고 이 템플릿의 인스턴스를 배열의 각 영웅마다 변환한다.


The let hero part of the expression identifies hero as the template input variable, which holds the current hero item for each iteration. You can reference this variable within the template to access the current hero's properties.


표현식의 let hero 부분은 hero를 템플릿 input변수로 나타낸다. 결국 템플릿 input변수 hero는 각 반복마다 현재 hero아이템을 가지게 된다. 여러분은 현재 영웅의 속성을 알기 위해 이 변수를 템플릿과 함께 참조 할 수 있다.


Read more about ngFor and template input variables in the Showing an array property with *ngFor section of the Displaying Data page and the ngFor section of the Template Syntax page. Within the <li>tags, add content that uses the hero template variable to display the hero's properties. 


ngFor과 템플릿 input변수에 대해 ‘데이터 보여주기’페이지의 ’*ngFor로 배열의 속성보여주기’섹션과 ‘템플릿 문법’페이지의 ‘ngFor’ 섹션에서 더 읽어보아라. app.component.ts (ngFor template)

<li *ngFor="let hero of heroes">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

When the browser refreshes, a list of heroes appears.


브라우저가 새로고침되면 영웅의 리스트가 나타난다.


Style the heroes


영웅에 스타일 더하기


Users should get a visual cue of which hero they are hovering over and which hero is selected.


사용자들은 그들이 어떤 영웅위에 hover하고 있는지 그리고 어떤영웅이 선택되었는지 시각적인 효과를 필요로한다.


To add styles to your component, set the styles property on the @Component decorator to the following CSS classes:


여러분의 component에 스타일을 더하기 위해 @Component 데코레이터에 스타일 속성을 다음 CSS 클래스들로 놓아라:


srcappapp.component.ts (styles)

styles: [`
  .selected {
    background-color: #CFD8DC !important;
    color: white;
  }
  .heroes {
    margin: 0 0 2em 0;
    list-style-type: none;
    padding: 0;
    width: 15em;
  }
  .heroes li {
    cursor: pointer;
    position: relative;
    left: 0;
    background-color: #EEE;
    margin: .5em;
    padding: .3em 0;
    height: 1.6em;
    border-radius: 4px;
  }
  .heroes li.selected:hover {
    background-color: #BBD8DC !important;
    color: white;
  }
  .heroes li:hover {
    color: #607D8B;
    background-color: #DDD;
    left: .1em;
  }
  .heroes .text {
    position: relative;
    top: -3px;
  }
  .heroes .badge {
    display: inline-block;
    font-size: small;
    color: white;
    padding: 0.8em 0.7em 0 0.7em;
    background-color: #607D8B;
    line-height: 1em;
    position: relative;
    left: -1px;
    top: -4px;
    height: 1.8em;
    margin-right: .8em;
    border-radius: 4px 0 0 4px;
  }
`]

Remember to use the backtick notation for multi-line strings.


여러줄의 문자열을 다룰때 backtick 표기법을 쓰는것을 잊지마라.


Adding these styles makes the file much longer. In a later page you'll move the styles to a separate file.


이런 스타일을 추가하는것은 파일을 더 길게 만든다. 나중에 여러분은 스타일들을 분리된 파일로 옮길것이다.


When you assign styles to a component, they are scoped to that specific component. These styles apply only to the AppComponent and don't affect the outer HTML.


component에 스타일을 지정했다면 그것들은 특정 component에 한정된다. 이러한 스타일은 AppComponent에만 적용되고 밖의 HTML에는 영향을 미치지 못한다.


The template for displaying heroes should look like this:


영웅들을 보여주기 위한 템플릿은 이래야 한다:


srcappapp.component.ts (styled heroes)

<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>

Selecting a hero

영웅 고르기


The app now displays a list of heroes as well as a single hero in the details view. But the list and the details view are not connected. When users select a hero from the list, the selected hero should appear in the details view. This UI pattern is known as "master/detail." In this case, the master is the heroes list and the detail is the selected hero.


이제 앱은 영웅들의 목록 뿐만아니라 한 영웅의 세부사항 뷰까지도 보여준다. 하지만 리스트와 세부사항뷰는 관계가 없다. 사용자들이 리스트에서 영웅을 골랐을때, 선택된 영웅은 세부사항뷰에 나타나야 한다. 이 UI패턴은 “마스터/디테일”로 알려졌다. 이 경우에 마스터는 영웅의 리스트이고, 디테일은 선택된 영웅이다.


Next you'll connect the master to the detail through a selectedHero component property, which is bound to a click event.


이제 여러분은 selectedHero component속성을 통해 마스터를 디테일에 연결시킬 것이다. 이는 클릭 이벤트를 하게 만든다.


Add a click event

클릭 이벤트 추가하기


Add a click event binding to the <li>like this:

<li>에 다음과 같이 클릭이벤트가 바인드 되도록 다음과 같이 클릭이벤트를 추가한다:


app.component.ts (template excerpt)

<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
  ...
</li>

The parentheses identify the <li> element's click event as the target. The onSelect(hero) expression calls the AppComponent method, onSelect(), passing the template input variable hero, as an argument. That's the same hero variable you defined previously in the ngFor directive.


구문은 <li>element의 클릭 이벤트를 타겟으로 나타낸다. onSelecr(hero) 표현은 AppComponent 메소드를 부르고, onSelect()는 템플릿 input변수 hero를 아규멘트로 전달한다. 이것은 여러분이 전에 ngFor 지시어에서 정의한 같은 hero변수이다.


Learn more about event binding at the User Input page and the Event binding section of the Template Syntax page.


사용자 input페이지와 템플릿 문법페이지의 이벤트 바인딩 섹션에서 이벤트 바인딩에 대해 더 아라보아라.


Add a click handler to expose the selected hero


선택된 영웅을 노출시키기 위해 믈릭 핸들러를 추가하라.


You no longer need the hero property because you're no longer displaying a single hero; you're displaying a list of heroes. But the user will be able to select one of the heroes by clicking on it. So replace the hero property with this simple selectedHero property:


여러분은 한 영웅만을 보여주지 않기에 hero속성이 더이상 필요하지 않다. 여러분은 영웅리스트를 보여준다. 따라서 사용자는 사용자는 영웅을 클릭함으로서 한 영웅을 선택 할 수 있다. 이제 영웅 속성을 이 간단한 선택된 영웅 속성으로 바꾸자:


srcappapp.component.ts (selectedHero)

selectedHero: Hero;


The hero names should all be unselected before the user picks a hero, so you won't initialize the selectedHero as you did with hero.


영웅의 이름들은 사용자가 영웅을 고르기 전까지 모두 선택되지 않아야 한다. 그래서 여러분은 여러분이 영웅에게 했던것 처럼 선택된 영웅을 초기화하지 말아야 한다.


Add an onSelect method that sets the selectedHero property to the hero that the user clicks.


유저가 선택한 영웅을 선택된 영웅 속성으로 하는 onSelect 메소드를 추가하라.


srcappapp.component.ts (onSelect)

onSelect(hero: Hero): void {
  this.selectedHero = hero;
}

The template still refers to the old hero property. Bind to the new selectedHero property instead as follows:


위 템플릿은 아직도 옛 영웅 속성을 가르킨다. 다음과 같이 새로운 selectedHero 속성에 대신 바인드해라:


app.component.ts (template excerpt)

<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
    <label>name: </label>
    <input [(ngModel)]="selectedHero.name" placeholder="name"/>
</div>

Hide the empty detail with ngIf


비어있는 세부사항을 nglf로 숨기기


When the app loads, the selectedHero is undefined and won't be defined until you click a hero's name. Angular can't display properties of the undefined selectedHero and throws the following error, visible in the browser's console:


앱이 로딩될 때, 선택된 영웅은 정의되지 않아있고 여러분이 영웅의 이름을 선택하기 전까지 정의되지 말아야 한다. Angular는 정의되지 않은 선택된 영웅을 보여줄 수 없고 브라우저 콘솔에 다음과 같은 에러를 낸다:


EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]

Although selectedHero.name is displayed in the template, you must keep the hero detail out of the DOM until there is a selected hero.


Wrap the HTML hero detail content of the template with a

. Then add the ngIf built-in directive and set it to the selectedHero property of the component.


템플릿의 HTML로된 영웅의 세부사항 콘텐츠를

로 감싸라. 그리고 nglf 내장 지시어를 추가하고 그것을 component의 선택된 영웅 속성에 놓아라.


srcappapp.component.ts (ngIf)

<div *ngIf="selectedHero">
  <h2>{{selectedHero.name}} details!</h2>
  <div><label>id: </label>{{selectedHero.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="selectedHero.name" placeholder="name"/>
  </div>
</div>

Don't forget the asterisk (*) in front of ngIf.

The app no longer fails and the list of names displays again in the browser.


nglf앞에 에스테릭을 잊지 마라.

앱은 더이상 실패하지 않고 브라우저에는 이름의 목록이 다시 나타난다.


When there is no selectedHero, the ngIf directive removes the hero detail HTML from the DOM. There are no hero detail elements or bindings to worry about.


선택된 영웅이 없을때, nglf지시어는 영웅의 세부사항 HTML을 DOM으로부터 제거한다. 영웅의 세부사항 요소나 걱정할 바인딩은 없다.


When the user picks a hero, selectedHero becomes defined and ngIf puts the hero detail content into the DOM and evaluates the nested bindings.


사용자가 영웅을 선택할때, 선택된 영웅은 정의되고 nglf는 영웅의 세부 사항 콘텐츠를 DOM에 넣고 괄호로 감싼 바인딩을 확인한다.


Read more about ngIf and ngFor in the Structural Directives page and the Built-in directives section of the Template Syntax page.


구조적 지시어 페이지와 템플릿 문법페이지의 내장 지시어 섹션에서 nglf와 ngFor에 대해 더 읽어보라.


Style the selected hero


선택된 영웅의 스타일 적용


While the selected hero details appear below the list, it's difficult to identify the selected hero within the list itself.


선택된 영웅의 세부사항이 리스트. 아래 나타날때, 리스트 자체와 함께 선택된 영웅을 확인하는 것은 어렵다.


In the styles metadata that you added above, there is a custom CSS class named selected. To make the selected hero more visible, you'll apply this selected class to the <li> when the user clicks on a hero name. For example, when the user clicks "Magneta", it should render with a distinctive but subtle background color like this:


여러분이 위에서 더한 스타일 메타데이터에는 selected라는 커스텀 CSS클래스가 있다. 선택된 영웅을 더 눈에 띄이게 하도록 여러분은 사용자가 영웅의 이름을 클릭할때 <li>에 이 selected클래스를 적용할 것이다. 예를 들어, 사용자가 “Magneta”를 클릭할 때, 이것은 구별되지만 미묘한 배경색깔을 이렇게 렌더링한다:


In the template, add the following [class.selected] binding to the <li>:


템플릿에서, 다음 [class.selected] 바인딩을 <li>에 추가하라.


app.component.ts (setting the CSS class)

[class.selected]="hero === selectedHero"


When the expression (hero === selectedHero) is true, Angular adds the selected CSS class. When the expression is false, Angular removes the selected class.


표현식 (hero === selectedHero)이 사실 이라면 Angular는 selected CSS 믈래스를 추가한다. 표현식이 거짓이면 Angular는 selected클래스를 제거한다.


Read more about the [class] binding in the Template Syntax guide.

The final version of the <li> looks like this:


[class]바인딩에 관해 템플릿 문법가이드에서 더 읽어보라.

마지막 <li>의 버전은 다음과 같다:


app.component.ts (styling each hero)

<li *ngFor="let hero of heroes"
  [class.selected]="hero === selectedHero"
  (click)="onSelect(hero)">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

After clicking "Magneta", the list should look like this:


“Magneta”를 클릭하면 리스트는 이렇게 나타난다:


Here's the complete app.component.ts as of now:


여기에 완벽한 app.component.ts가 있다:


srcappapp.component.ts

import { Component } from '@angular/core';
export class Hero {
  id: number;
  name: string;
}
const HEROES: Hero[] = [
  { id: 11, name: 'Mr. Nice' },
  { id: 12, name: 'Narco' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes"
        [class.selected]="hero === selectedHero"
        (click)="onSelect(hero)">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    <div *ngIf="selectedHero">
      <h2>{{selectedHero.name}} details!</h2>
      <div><label>id: </label>{{selectedHero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="selectedHero.name" placeholder="name"/>
      </div>
    </div>
  `,
  styles: [`
    .selected {
      background-color: #CFD8DC !important;
      color: white;
    }
    .heroes {
      margin: 0 0 2em 0;
      list-style-type: none;
      padding: 0;
      width: 15em;
    }
    .heroes li {
      cursor: pointer;
      position: relative;
      left: 0;
      background-color: #EEE;
      margin: .5em;
      padding: .3em 0;
      height: 1.6em;
      border-radius: 4px;
    }
    .heroes li.selected:hover {
      background-color: #BBD8DC !important;
      color: white;
    }
    .heroes li:hover {
      color: #607D8B;
      background-color: #DDD;
      left: .1em;
    }
    .heroes .text {
      position: relative;
      top: -3px;
    }
    .heroes .badge {
      display: inline-block;
      font-size: small;
      color: white;
      padding: 0.8em 0.7em 0 0.7em;
      background-color: #607D8B;
      line-height: 1em;
      position: relative;
      left: -1px;
      top: -4px;
      height: 1.8em;
      margin-right: .8em;
      border-radius: 4px 0 0 4px;
    }
  `]
})
export class AppComponent {
  title = 'Tour of Heroes';
  heroes = HEROES;
  selectedHero: Hero;
  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}

The road you've travelled

여러분이 걸어온 길


Here's what you achieved in this page:


여기 여러분이 이 페이지에서 이룬것들이 있다:


The Tour of Heroes app displays a list of selectable heroes.

You added the ability to select a hero and show the hero's details.

You learned how to use the built-in directives ngIf and ngFor in a component's template.

Your app should look like this live example / downloadable example.


The Tour of Heroes 앱은 선택가능한 영웅들의 목록을 보여준다.

여러분은 영웅을 선택할 수 있게 했고, 영웅들의 세부사항을 보여줄 수 있게 ㅁ추가했다.

여러분은 component의 템플릿에서 어떻게 내부 지시어 nglf와 ngFor을 사용하는지 배웠다.

여러분의 앱은 다음 예와 같아야 한다.


The road ahead

앞으로의 길


You've expanded the Tour of Heroes app, but it's far from complete. You can't put the entire app into a single component. In the next page, you'll split the app into sub-components and make them work together.


여러분은 Tour of Heroes앱을 확장했으나 완벽하기엔 이르다. 여러분은 모든 앱을 한 컴포넌트에 넣었다. 다음페이지에서 여러분은 앱을 sub-component에 넣고 이들이 함께 동작할 수 있도록 할 것이다.


'Angular' 카테고리의 다른 글

(번역)Angular tutorials - The Tour of Heroes - THE HERO EDITOR  (0) 2017.04.01
(번역)Angular - User Input  (0) 2017.03.29

THE HERO EDITOR - Build a simple hero editor.

Setup to develop locally


Follow the setup instructions for creating a new project named angular-tour-of-heroes.


셋업과정을 따라하여 ‘angular-tour-of-heroes’라는 새 프로젝트를 만드세요.


Keep the app transpiling and running

앱을 실행시켜놓고 transcompile상태에 두기


Enter the following command in the terminal window:

npm start


터미널에 다음 명령어를 실행합니다:

npm start


This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes. The command simultaneously launches the app in a browser and refreshes the browser when the code changes.


이 명령어는 Typescript compiler를 “watch mode”에 둡니다. 코드가 바뀌면 자동으로 재컴파일됩니다.


You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.


여러분은 Tour of Heroes를 정지시켜 재컴파일시키거나 브라우저를 새로고침하지 않고 계속해서 만들 수 있게 되었습니다.



Show the hero

영웅 보여주기


Add two properties to the AppComponent: a title property for the app name and a hero property for a hero named "Windstorm."


AppComponent에 두가지 속성을 추가합니다: 앱이름의 타이틀 속성과 “Windstorm”이라고 불리는 영웅을 위한 영웅속성입니다.


app.component.ts (AppComponent class)

export class AppComponent {
  title = 'Tour of Heroes';
  hero = 'Windstorm';
}

Now update the template in the @Component decorator with data bindings to these new properties.


이제 @Component 데코레이터에 위의 두 속성의 데이터바인딩으로 템플릿을 업데이트하세요.


app.component.ts (@Component)

template: `<h1>{{title}}</h1><h2>{{hero}} details!</h2>`

The browser refreshes and displays the title and hero name.


브라우저는 새로고침을 하고 제목과 영웅의 이름을 보여줄 것 입니다.


The double curly braces are Angular's interpolation binding syntax. These interpolation bindings present the component's title and hero property values, as strings, inside the HTML header tags.

Read more about interpolation in the Displaying Data page.


더블 중괄호는 Angular의 interpolation binding 문법입니다. 이러한 interpolation binding들은 component의 타이틀과 영웅속성의 값을 HTML헤더 태그 안에서 나타냅니다.


Hero object

영웅 객체


The hero needs more properties. Convert the hero from a literal string to a class.


영웅들은 속성이 더 필요합니다. 영웅을 리터럴상수에서 클래스로 바꿉시다.


Create a Hero class with id and name properties. Add these properties near the top of the app.component.ts file, just below the import statement.


Hero 클래스를 id와 이름 속성으로 만듭니다. 이 속성들을 app.components.ts파일의 시작부분 근처에 추가합시다. import구문 아래 말이죠.


srcappapp.component.ts (Hero class)

export class Hero {
  id: number;
  name: string;
}

In the Hero class, refactor the component's hero property to be of type Hero, then initialize it with an id of 1 and the name Windstorm.


Hero객체 안에서, component의 hero속성이 Hero의 탐입이 되도록 refactor한다. 그리고 id를 1, 이름을 Windstorm으로 초기화합니다.


srcappapp.component.ts (hero property)

hero: Hero = {
  id: 1,
  name: 'Windstorm'
};

Because you changed the hero from a string to an object, update the binding in the template to refer to the hero's name property.


여러분이 hero를 문자열에서 객체로 바꾸었기 때문에, 템플릿의 바인딩을 영웅의 이름이 속성을 뜻하도록 업데이트 합니다.


template: `<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>`

The browser refreshes and continues to display the hero's name.


브라우저는 새로고쳐지고 계속해서 영웅의 이름을 보여줍니다.


Adding HTML with multi-line template strings

여러줄의 템플릿 문자열과 함께 HTML추가하기.


To show all of the hero's properties, add a

for the hero's id property and another
for the hero's name. To keep the template readable, place each
on its own line.


영웅의 모든 속성을 보여주기 위해서,

를 영웅의 id속성과 이름에 각각 더한다. 템플릿이 읽기 좋도록
를 각 라인마다 두자.


The backticks around the component template let you put the

,

, and
elements on their own lines, thanks to the template literals feature in ES2015 and TypeScript. For more information, see Template literals.


component 템플릿 주변의 backtick들은

,

, 그리고
요소들을 각각 그들의 라인에 있을 수 있도록 한다. 자세한 정보는 템플릿 리터럴을 참고하자.


app.component.ts (AppComponent's template)

template: `
  <h1>{{title}}</h1>
  <h2>{{hero.name}} details!</h2>
  <div><label>id: </label>{{hero.id}}</div>
  <div><label>name: </label>{{hero.name}}</div>
  `

Edit the hero name

영웅의 이름 수정하기


Users should be able to edit the hero name in an textbox. The textbox should both display the hero's name property and update that property as the user types.


사용자들은 텍스트 상자에 영웅의 이름을 수정할 수 있어야 한다. 텍스트상자는 영웅의 이름속성을 보여주기도 하고 사용자가 수정할때 업데이트되어야한다.


You need a two-way binding between the form element and the hero.name property.


여러분은 폼 요소와 hero.name속성의 두가지 바인딩이 필요하다.


Two-way binding

두가지 바인딩


Refactor the hero name in the template so it looks like this:


템플릿의 영웅의 이름을 다음과 같이 보이도록 리팩토링하자:


<div>
  <label>name: </label>
  <input [(ngModel)]="hero.name" placeholder="name">
</div>

[(ngModel)] is the Angular syntax to bind the hero.name property to the textbox. Data flow in both directions: from the property to the textbox; and from the textbox back to the property.


[(ngModel)]은 hero.name속성을 텍스트상자에 묶기 위한 Angular의 문법이다. 데이터는 양방향으로 전달됩니다: 속성에서 텍스트상자로, 그리고 텍스트상자에서 속성으로


Unfortunately, immediately after this change, the application breaks. If you looked in the browser console, you'd see Angular complaining that "ngModel ... isn't a known property of input."


안타깝게도, 이 변화 이후로 앱이 깨진다. 만약 여러분이 브라우저 콘솔을 보게되면, Angular가 “ngModel은 input의 속성이 아닙니다”라고 하는걸 볼 수 있다.


Although NgModel is a valid Angular directive, it isn't available by default. It belongs to the optional FormsModule. You must opt-in to using that module.


NgModel은 Angular의 지시어이긴 하지만 디폴트로 지원하지는 않는다. 옵션적인 FormsModule에 속해있다.


Import the FormsModule


FormsModule을 import하자


Open the app.module.ts file and import the FormsModule symbol from the @angular/forms library. Then add the FormsModule to the @NgModule metadata's imports array, which contains the list of external modules that the app uses.


App.moule.ts 파일을 열고 FormsModule 심볼을 @angular/forms 라이브러리에서 import한다.


The updated AppModule looks like this:


업데이트된 AppModule은 이렇게 보인다:


app.module.ts (FormsModule import)

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms'; // <-- NgModel lives here
import { AppComponent }  from './app.component';
@NgModule({
  imports: [
    BrowserModule,
    FormsModule // <-- import the FormsModule before binding with [(ngModel)]
  ],
  declarations: [
    AppComponent
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

When the browser refreshes, the app should work again. You can edit the hero's name and see the changes reflected immediately in the

above the textbox.


브라우저가 새로고침되면, 앱은 다시 실행될 것이다. 여러분은 영웅의 이름을 수정하면 텍스트상자 위

에서 바로 변화가 일어나는걸 볼 수 있다.




The road you've travelled


이제까지 당신이 한 일들


Take stock of what you've built.


여러분이 이룬것을 보자.


The Tour of Heroes app uses the double curly braces of interpolation (a type of one-way data binding) to display the app title and properties of a Hero object.

You wrote a multi-line template using ES2015's template literals to make the template readable.

You added a two-way data binding to the element using the built-in ngModel directive. This binding both displays the hero's name and allows users to change it.

The ngModel directive propagates changes to every other binding of the hero.name.

Your app should look like this live example / downloadable example.


Tour of Heroes앱은 더블 중괄호의 interpolation(한방향으로 데이터를 묶는것)을 사용한다. 이를 통해 앱의 타이틀과 Hero 객체의 속성을 보여준다.

여러분은 ES2015의 템플릿 리터럴상수를 사용해 템플릿을 읽기 좋게 여러줄로 적었다.


Here's the complete app.component.ts as it stands now:


완벽한 app.component.ts는 다음과 같다:


srcappapp.component.ts

import { Component } from '@angular/core';
export class Hero {
  id: number;
  name: string;
}
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <h2>{{hero.name}} details!</h2>
    <div><label>id: </label>{{hero.id}}</div>
    <div>
      <label>name: </label>
      <input [(ngModel)]="hero.name" placeholder="name">
    </div>
    `
})
export class AppComponent {
  title = 'Tour of Heroes';
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}

The road ahead


In the next tutorial page, you'll build on the Tour of Heroes app to display a list of heroes. You'll also allow the user to select heroes and display their details. You'll learn more about how to retrieve lists and bind them to the template.


다음 튜토리얼에서 여러분은 영웅들의 리스트를 보여주기 위해 Tour of Heroes앱을 만들것이다. 여러분은 또한 사용자들에게 영웅을 선택하고 그들의 세부사항을 볼 수 있도록 만들것이다. 여러분은 어떻게 리스트를 검색하고 템플릿에 바인닝 할지 더 배울것이다.

'Angular' 카테고리의 다른 글

(번역)Angular tutorial - The Tour of Heroes - MASTER/DETAIL  (0) 2017.04.06
(번역)Angular - User Input  (0) 2017.03.29

(번역)Angular - User Input

User input triggers DOM events. We listen to those events with event bindings that funnel updated values back into our components and models.


User input은 DOM 이벤트를 실행시킵니다. 우리는 그 이벤트들을 component들과 model들에게 업데이트된 값을 제공하는 이벤트 바인딩을 통해 듣습니다.


User actions such as clicking a link, pushing a button, and entering text raise DOM events. This page explains how to bind those events to component event handlers using the Angular event binding syntax.


링크나 버튼을 눌러 텍스트를 타고 들어가는 유저들의 액션은 DOM 이벤트를 일으킵니다. 이 페이지는 그러한 이벤트들을 어떻게 Angular syntax를 이용해 component이벤트 핸들러들로 묶을 수 있는지 설명합니다.



Binding to user input events

User input이벤트에 바인딩하기(묶기)


You can use Angular event bindings to respond to any DOM event. Many DOM events are triggered by user input. Binding to these events provides a way to get input from the user.


여러분은 Angular이벤트 바인딩을 어느 DOM이벤트에나 응답하도록 사용할 수 있습니다. 이런 DOM이벤트에 바인딩을 해두면 사용자들로부터 input을 받을 수 있게됩니다.


To bind to a DOM event, surround the DOM event name in parentheses and assign a quoted template statement to it.


DOM이벤트에 바인딩하기 위해서는 DOM이벤트 이름을 괄호로 묶고 인용된 template문을 적용하면 됩니다.


The following example shows an event binding that implements a click handler:


다음 예는 click 핸들러를 제공하는 이벤트바인딩의 예를 보여줍니다:


The (click) to the left of the equals sign identifies the button's click event as the target of the binding. The text in quotes to the right of the equals sign is the template statement, which responds to the click event by calling the component's onClickMe method.


등호표시 왼쪽의 ‘(click)’은 버튼의 클릭 이벤트를 바인딩타겟으로 할 것이라는걸 의미합니다. 등호 오른쪽에 있는 따옴표안의 텍스트는 template statement입니다. Template statement는 component의 onClickMe 메소드를 호출함으로서 click 이벤트에 응답합니다.


When writing a binding, be aware of a template statement's execution context. The identifiers in a template statement belong to a specific context object, usually the Angular component controlling the template. The example above shows a single line of HTML, but that HTML belongs to a larger component:


바인딩을 할때, template statement의 문맥을 주의하세요. Template statement에 있는 식별자는 특정 context object(주로 템플릿을 제어하는 Angular component)에 속합니다. 아래 예제는 한 줄의 HTML을 보여주지만 이 HTML은 더 큰 component에 속합니다:


srcappclick-me.component.ts

@Component({
selector: 'click-me',
  template: `
    <button (click)="onClickMe()">Click me!</button>
    {{clickMessage}}`
})
export class ClickMeComponent {
  clickMessage = '';

  onClickMe() {
    this.clickMessage = 'You are my hero!';
  }
}
When the user clicks the button, Angular calls the onClickMe method from ClickMeComponent. 사용자가 버튼을 누르면, Angular은 ClickMeComponent에서 ClickMe메소드를 호출합니다.

Get user input from the $event object

$event 객체로부터 사용자에게 입력값 받기


DOM events carry a payload of information that may be useful to the component. This section shows how to bind to the keyup event of an input box to get the user's input after each keystroke.


DOM이벤트들은 component에 유용할 지도 모르는 많은 정보를 가지고 다닌다. 이 섹션은 input박스의 keyup이벤트를 바인드해 사용자가 키보드를 두드릴때마다 입력값을 받을 수 있도록 한다.


The following code listens to the keyup event and passes the entire event payload ($event) to the component event handler.


다음 코드는 keyup이벤트를 들은 후 전체 이벤트 payload($event)를 component 이벤트핸들러로 보냅니다.


srcappkeyup.components.ts (template v.1)

template: `
  <input (keyup)="onKey($event)">
  <p>{{values}}</p>
`

When a user presses and releases a key, the keyup event occurs, and Angular provides a corresponding DOM event object in the $event variable which this code passes as a parameter to the component's onKey() method.


사용자가 키를 누르고 떼면, keyup이벤트가 발생하는데, Angular은 이에 대응하는 DOM이벤트 객체를 $event 변수에서 제공합니다. 여기 코드에서는 component의 onKey()메소드에 파라미터를 보냅니다.


srcappkeyup.components.ts (class v.1)

export class KeyUpComponent_v1 {
  values = '';

  onKey(event: any) { // without type info
    this.values += event.target.value + ' | ';
  }
}

The properties of an $event object vary depending on the type of DOM event. For example, a mouse event includes different information than a input box editing event.


$event 객체의 속성은 DOM이벤트의 다입에 따라 다양합니다. 예를 들어, 마우스 이벤트는 input박스를 수정하는 이벤트와는 다른 정보를 포함합니다.


All standard DOM event objects have a target property, a reference to the element that raised the event. In this case, target refers to the input element and event.target.value returns the current contents of that element.


모든 표준 DOM이벤트 객체는 타겟 속성을 가집니다. 타겟속성은 이벤트를 일으킨 element의 reference입니다. 이런 경우에, 타겟은 input요소를 의미하며 event.target.value는 그 요소의 현재 내용을 반환합니다.


After each call, the onKey() method appends the contents of the input box value to the list in the component's values property, followed by a separator character (|). The interpolation displays the accumulating input box changes from the values property.


매 호출마다, onKey()메소드는 input박스의 값을 component의 value속성에 구분자(|)를 넣어 붙입니다. 삽입문구는 값의 속성으로부터 축적된 input박스의 변화를 보여줍니다.


Suppose the user enters the letters "abc", and then backspaces to remove them one by one. Here's what the UI displays:

a | ab | abc | ab | a | |

사용자가 알파벳 “abc”를 입력했다가 하나씩 벡스페이스를 눌러 지웠다고 가정하자. UI는 다음과 같이 나타납니다:



Alternatively, you could accumulate the individual keys themselves by substituting event.key for event.target.value in which case the same user input would produce:

a | b | c | backspace | backspace | backspace |

다른 방법으로 여러분은 event.key를 event.target.value로 바꾸어서 각각의 키들을 축적할 수 있습니다. 이는 같은 결과를 보여줍니다:



Type the $event

$event 타이핑하기.


The example above casts the $event as an any type. That simplifies the code at a cost. There is no type information that could reveal properties of the event object and prevent silly mistakes.


위의 예제는 $event를 어느 타입으로든 던져줍니다. 이는 그에 따르는 대가가 있지만 코드를 간단하게 만들어 줍니다. 해당 이벤트 객체의 속성을 알 수 있는 타입정보가 없는 대신 멍청한 실수를 막을 수 있습니다.


The following example rewrites the method with types:


다음 예제는 메소드를 타입과 함께 다시 쓴것입니다:


srcappkeyup.components.ts (class v.1 - typed )

export class KeyUpComponent_v1 {
  values = '';


  onKey(event: KeyboardEvent) { // with type info
    this.values += (<HTMLInputElement>event.target).value + ' | ';
  }
}

The $event is now a specific KeyboardEvent. Not all elements have a value property so it casts target to an input element. The OnKey method more clearly expresses what it expects from the template and how it interprets the event.


$event는 이제 특정 KeyboardEvent입니다. 모든 요소들이 값속성을 가진것이 아니기 때문에 input요소에 타겟을 집어넣습니다. OnKey메소드는 템플릿으로부터 무엇을 기대하는지 그리고 어떻게 이벤트를 해석하는지 명료하게 표현합니다.


Passing $event is a dubious practice


$event를 전달하는것은 미심쩍은 표현입니다.


Typing the event object reveals a significant objection to passing the entire DOM event into the method: the component has too much awareness of the template details. It can't extract information without knowing more than it should about the HTML implementation. That breaks the separation of concerns between the template (what the user sees) and the component (how the application processes user data).


이벤트 객체를 적어 넣는것은 메소드에 전체 DOM이벤트를 전달하는것에 정말 반대한다는 뜻입니다: component는 템플릿의 세부사항에 대해 너무 많이 알고있습니다. 이는 HTML을 이식하는데 알아야 하는만큼보다 더 많이 알지 못하면 정보를 가져올 수 없습니다. 이는 템플릿(사용자들이 보는것)과 component(어떻게 어플리케이션이 사용자의 데이터를 이용하는지)의 분열 갈등을 부숩니다.


The next section shows how to use template reference variables to address this problem.


다음 섹션은 이러한 문제를 위해 어떻게 템플릿 참조변수를 사용할지 보여줍니다.



Get user input from a template reference variable

템플릿 참조변수로부터 사용자의 input받기.


There's another way to get the user data: use Angular template reference variables. These variables provide direct access to an element from within the template. To declare a template reference variable, precede an identifier with a hash (or pound) character (# ).


사용자의 데이터를 받을 수 있는 다른 방법이 있습니다: Angular 템플릿 참조변수를 사용하세요. 이 변수들은 템플릿 안에서 요소들에 직접적인 접근을 허용합니다. 템플릿의 참조변수를 선언하기 위해 해쉬(또는 우물정자) 글자#을 선행하세요.


The following example uses a template reference variable to implement a keystroke loopback in a simple template.


다음 예제는 간단한 템플릿으로 키보드 loopback을 시행하기 위해 템플릿 참조변수를 사용합니다.


srcapploop-back.component.ts

@Component({
  selector: 'loop-back',
  template: `
    <input #box (keyup)="0">
    <p>{{box.value}}</p>
  `
})
export class LoopbackComponent { }

The template reference variable named box, declared on the input element, refers to the input element itself. The code uses the box variable to get the input element's value and display it with interpolation between

tags.


box라고 불리는 템플릿 참조변수(input 요소에 선언되어있는)는 input요소 그자체를 나타냅니다. 또 코드는 box변수를 이용해 input요소의 값을 얻어오고

태그 사이에 그 값을 써넣습니다.


The template is completely self contained. It doesn't bind to the component, and the component does nothing.


이 템플릿은 철저히 자신을 내포하고 있다. component에 바인딩되어 있지도 않고 component는 아무것도 하지 않습니다.


Type something in the input box, and watch the display update with each keystroke.


input박스에 무언가 써넣어보고 키보드를 누를때마다 화면이 변경되는것을 보세요.


>This won't work at all unless you bind to an event.


이것은 여러분이 이벤트를 바인드 하지 않는 이상 작동하지 않을 것 입니다.


Angular updates the bindings (and therefore the screen) only if the app does something in response to asynchronous events, such as keystrokes. This example code binds the keyup event to the number 0, the shortest template statement possible. While the statement does nothing useful, it satisfies Angular's requirement so that Angular will update the screen.

It's easier to get to the input box with the template reference variable than to go through the $event object. Here's a rewrite of the previous keyup example that uses a template reference variable to get the user's input.


Angular는 바인딩을(따라서 스크린을) 앱이 비동기 이벤트들에 대한 응답으로 무언가를 할 때에만 업데이트합니다. 키보드를 두드리는 행위가 그러한 예입니다. 이 예제코드는 keyup이벤트를 가장 짧은 템플릿문인 숫자 0에 바인드 합니다. 템플릿문이 유용한 일을 하지는 않지만 Angular의 필요사항을 충족시켜 Angular가 화면을 업데이트 할것입니다. input박스에 접근하는것은 $event 객체를 통해 접근 하는 방법보다 템플릿 참조변수로 접근하는 것이 더 쉽습니다. 여기 이전의 템플릿 참조변수를 활용해 사용자의 input을 가져오는 keyup 예제를 다시 적었습니다.


srcappkeyup.components.ts (v2)

@Component({
  selector: 'key-up2',
  template: `
    <input #box (keyup)="onKey(box.value)">
    <p>{{values}}</p>
  `
})
export class KeyUpComponent_v2 {
  values = '';
  onKey(value: string) {
    this.values += value + ' | ';
  }
}

A nice aspect of this approach is that the component gets clean data values from the view. It no longer requires knowledge of the $event and its structure.


이러한 접근의 좋은점은 component가 뷰로부터 명확한 데이터를 가져온다는 것입니다. 또 $event와 구조에 대한 지식이 필요가 없습니다.



Key event filtering (with key.enter)


The (keyup) event handler hears every keystroke. Sometimes only the Enter key matters, because it signals that the user has finished typing. One way to reduce the noise would be to examine every $event.keyCode and take action only when the key is Enter.


keyup이벤트 핸들러는 모든 키보드 칠 때의 변화를 듣습니다. 때로 사용자가 타이핑을 마쳤다는 신호를 주는 엔터키가 문제입니다. 이러한 문제를 해결 하는 한가지 방법은 $event.keyCode마다 검사하고 키가 엔터일 때만 조취를 취하도록 하는 것입니다.


There's an easier way: bind to Angular's keyup.enter pseudo-event. Then Angular calls the event handler only when the user presses Enter.


더 쉬운 방법이 있습니다: Angular의 keyup.enter를 pseudo-event로 묶는것입니다. 그러면 Angular는 이벤트 핸들러를 사용자가 엔터를 누를 때만 부를 것 입니다.


srcappkeyup.components.ts (v3)

@Component({
  selector: 'key-up3',
  template: `
    <input #box (keyup.enter)="onEnter(box.value)">
    <p>{{value}}</p>
  `
})
export class KeyUpComponent_v3 {
  value = '';
  onEnter(value: string) { this.value = value; }
}

On blur

blur이벤트에서


In the previous example, the current state of the input box is lost if the user mouses away and clicks elsewhere on the page without first pressing Enter. The component's value property is updated only when the user presses Enter.


이전 예제에서, 사용자가 엔터를 우선치지 않고 마우스를 아무데나 누를때면 input박스의 현재 상태를 잃어버렸습니다. 그 component의 값속성은 사용자가 엔터를 칠 때만 업데이트되었습니다.


To fix this issue, listen to both the Enter key and the blur event.


이러한 이슈를 해결하기 위해, 엔터키와 blur이벤트를 모두 들어야 합니다.


srcappkeyup.components.ts (v4)

@Component({
  selector: 'key-up4',
  template: `
    <input #box
      (keyup.enter)="update(box.value)"
      (blur)="update(box.value)">

    <p>{{value}}</p>
  `
})
export class KeyUpComponent_v4 {
  value = '';
  update(value: string) { this.value = value; }
}

Put it all together 모두 합쳐봅시다. The previous page showed how to display data. This page demonstrated event binding techniques.


이전 페이지는 어떻게 데이터를 나타낼지에 관한 설명이었습니다. 이번 페이지는 증명된 이벤트 바인딩 테크닉에 관한 내용이었습니다.


Now, put it all together in a micro-app that can display a list of heroes and add new heroes to the list. The user can add a hero by typing the hero's name in the input box and clicking Add.


이제, 영웅 리스트를 보여주고 새로운 영웅을 추가 할 수 있는 마이크로-앱에 모두 넣어 봅시다. 사용자는 영웅의 이름을 input박스에 넣고 Add버튼을 눌러 영웅을 추가 할 수 있습니다.


Below is the "Little Tour of Heroes" component.


아래는 “작은 영웅의 여행” component입니다.


srcapplittle-tour.component.ts

@Component({
  selector: 'little-tour',
  template: `
    <input #newHero
      (keyup.enter)="addHero(newHero.value)"
      (blur)="addHero(newHero.value); newHero.value='' ">

    <button (click)="addHero(newHero.value)">Add</button>

    <ul><li *ngFor="let hero of heroes">{{hero}}</li></ul>
  `
})
export class LittleTourComponent {
  heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
  addHero(newHero: string) {
    if (newHero) {
      this.heroes.push(newHero);
    }
  }
}
Observations 관찰결과 Use template variables to refer to elements — The newHero template variable refers to the input element. You can reference newHero from any sibling or child of the input element. 


요소를 나태내기 위해 템플릿 변수를 사용하세요 — newHero 템플릿 변수는 input요소를 의미합니다. 여러분은 newHero변수를 input요소의 어떤 자매나 자식에게서 부터 참조할 수 있습니다.


Pass values, not elements — Instead of passing the newHero into the component's addHero method, get the input box value and pass that to addHero.


요소가 아니라, 값을 전달하세요. 


Keep template statements simple — The (blur) event is bound to two JavaScript statements. The first statement calls addHero. The second statement, newHero.value='', clears the input box after a new hero is added to the list.


템플릿변수를 간단하게 유지 하세요 — (blur)이벤트는 두 자바스크립트문을 가르킵니다. 처음 문장은 addHero를 부르고, 두번째 문장 newHero.value=‘’는 새로운 영웅이 리스트에 추가된 후 input상자를 지웁니다



Summary

요약 You have mastered the basic primitives for responding to user input and gestures.


여러분은 사용자의 입력과 제스쳐에 응답하는 기본적인 것들을 마스터했습니다.


These techniques are useful for small-scale demonstrations, but they quickly become verbose and clumsy when handling large amounts of user input. Two-way data binding is a more elegant and compact way to move values between data entry fields and model properties. The next page, Forms, explains how to write two-way bindings with NgModel.


이런 태크닉들은 작은단위의 증명에 유리합니다. 하지만 많은 양의 사용자 입력을 다룰 때면 이런것들은 장황해지고 어설퍼집니다. 두 방법으로 데이터를 바인딩 하는것은 데이터입력 필트와 모델 속성 사이에 값을 이동시키는 더 우아하고 최적화된 방법입니다. 다음페이지(Form)에서는 어떻게 NgModel을 가지고 두가지 바인딩을 쓸수 있는지 설명합니다.


+ Recent posts