Tutorial: Tour of Heroes - Display a List

kukudas·2022년 2월 14일
0

Angular

목록 보기
4/15

Create mock heroes

나중에는 서버에서 데이터를 받아와야 하지만 지금 당장은 서버에서 받아오는 것처럼하는 hero 데이터를 만들어야함.
mock-heroes.tssrc/app/에 만들어서 HEROES constant를 만들어줌.

import { Hero } from './hero';

export const HEROES: Hero[] = [
  { id: 11, name: 'Dr 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' }
];

Displaying heroes

HeroesComponent 클래스 파일에서 mock HEROES를 import 해주고 heroes 컴포넌트 property를 선언해서HEROES 배열을 바인딩을 위해 노출시켜줌.

// src/app/heroes/heroes.component.ts
import { HEROES } from '../mock-heroes';

export class HeroesComponent implements OnInit {

  heroes = HEROES;
}

List heroes with *ngFor

HeroesComponent 템플릿
파일을 열어서 아래처럼 바꿔줌.

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

지금 상태에서는 hero property가 존재하지 않아서 에러가 나옴. 각각의 hero에 접근해서 list로 보여주기 위해서는 *ngFor<li>에 추가해서 hero list를 순회할 수 있음.

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

*ngFor는 앵귤러 repeater directive임. *ngFor는 host element를 list안의 element 개수만큼 반복함.

위 코드에서 <li>가 host element고 let hero of heroes에서 heroesHeroesComponent 클래스에 있는 mock heroes 리스트를 가지고 있음. 이제 let hero of heroes에서 hero가 각각의 iteration에서 현재 hero 객체를 가지고 있음.

다 적용하면 템플릿이 아래처럼 됨.

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

Style the heroes

스타일링 할때 컴포넌트를 추가할 때마다 전체 앱을 위한 style.css에다가 스타일을 적용하거나 각각의 컴포넌트 마다 private style을 정해줄 수도 있음.

마지막 방법이 컴포넌트를 다른곳에서 재사용하기도 쉽고, 글로벌 스타일이 바뀌더라도 컴포넌트의 원래 모습을 유지하기 좋음.

private style을 사용하려면 @Component.styles 배열안에 인라인으로 선언하거나 @Component.styleUrls 배열에 스타일시트 파일로 선언하면 됨.

@Component.styles 배열에 인라인으로 하면 아래 예시처럼 하면됨.

@Component({
  styles: ['h2 { color: red; }']
})

CLI가 HeroesComponent를 맨 처음에 생성할 때, HeroesComponent를 위한 빈 heroes.component.css 스타일시트도 생성주는데 여기다가 @Component.styleUrls에 아래처럼 넣어줌.

// src/app/heroes/heroes.component.ts
@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})

@Component 메타데이터 안에 있는 스타일이랑 스타일시트는 해당 컴포넌트에만 적용되는 거임.

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})

예를 들어서 위 코드에서 heroes.component.cssHeroesComponent에만 적용되고 다른 컴포넌트에는 영향 안줌.

Viewing details

이제 유저가 리스트에 있는 히어로를 눌렀을때 해당 히어로에 대한 detail을 페이지 아래에 보여주도록 컴포넌트를 바꿀꺼임.

Add a click event binding

<!--
	heroes.component.html
-->
<li *ngFor="let hero of heroes">
  <button type="button" (click)="onSelect(hero)">

위는 앵귤러의 event biding syntax를 사용한 예시임.

click을 감싸고 있는 ()는 앵귤러한테 <button> element의 click 이벤트를 listen하도록 알려줌. 이제 유저가 <button>을 누르게 되면 앵귤러는 onSelect(hero)를 실행하게됨.

Add the click event handler

컴포넌트의 hero property를 selectedHero로 바꿔주고 할당은 안해줌. 앱이 시작했을 때는 선택된 hero가 없기 때문임.
아래처럼 selectedHero에 값을 할당해주는 onSelect() 메소드를 만들어줌.

// src/app/heroes/heroes.component.ts

// selectedHero? 에서 ?는 selectedHero의 값이 undefined거나 값이 있거나 둘 중하나라는 optional한 변수라는 것을 알려주는 것임.
selectedHero?: Hero;
onSelect(hero: Hero): void {
  this.selectedHero = hero;
}

Add a details section

지금까지 한 작업으로는 리스트의 히어로를 눌렀을 때 해당 히어로에 대한 detail을 보여줄 곳이 없기에 아래처럼 맘ㄴ들어줌.

<!--
	heroes.component.html
-->
<h2>{{selectedHero.name | uppercase}} Details</h2>
<div>id: {{selectedHero.id}}</div>
<div>
  <label for="hero-name">Hero name: </label>
  <input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
</div>

이제 브라우저를 새로고침 해보면 앱이 작동안하는 것을 볼 수 있음.
에러 메시지는 아래처럼 나오는데 앱이 시작했을때 selectedHero가 디자인상으로 undefined라서 {{selectedHero.name}}처럼 selectedHero를 참조하는게 무조건 실패하기 때문임.

src/app/heroes/heroes.component.html:11:20 - error TS2532: Object is possibly 'undefined'.

11 <h2>{{selectedHero.name | uppercase}} Details</h2>

이걸 고치려면 컴포넌트는 selectedHero가 존재할때(undefined)가 아닐 때 selected hero의 detail을 보여줘야함.
hero detail을 <div>안에 감싸고 *ngIf<div>에 넣고 selectedHero랑 연결시킴. 이제 selectedHero가 존재하면 앵귤러가 <div>를 render 해줌.

NgIf는 템플릿을 조건에 따라서 포함시킴. expresstion이 true면 앵귤러는 템플릿을 then절로 랜더해주고 false나 null이면 옵션으로 제공된 else절로 랜더해줌.
default로 else절은 공백임.
then이 없으면 true일때 그냥 render 해줌.

ngIf에서 은 directive인데 앵귤러가 알아보기 쉽게 그냥 붙이는거임.
그냥 <div ngIf="selectedHero"> 해버리면 html attribute인지 앵귤러 directive인지알 수가 없음.

<!--
	src/app/heroes/heroes.component.html 
-->
<div *ngIf="selectedHero">

  <h2>{{selectedHero.name | uppercase}} Details</h2>
  <div>id: {{selectedHero.id}}</div>
  <div>
    <label for="hero-name">Hero name: </label>
    <input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
  </div>

</div>

이제 새로고침해서 보면 처음에는 detail이 비어있지만 리스트에서 heor를 누르면 detail이 표시되는것을 볼 수 있음.

Why it works

selectedHero가 undefined일 때는 ngIf가 hero detail을 DOM에서 없애버리기 때문에 selectedHero바인딩이 없음.

이제 유저가 hero를 누르면 selectedHero는 값을 가지게 되서 ngIf가 hero detail을 DOM에 집어 넣어서 보이게 되는거임.

Style the selected hero

클릭한 히어로를 알아보기 쉽게 하기 위해서 위에서 추가했던 .selected CSS 클래스를 사용하면됨. 유저가 클릭했을때만 .selected<li>에 적용하고 싶으면 클래스 바인딩을 사용하면 됨.

앵귤러의 클래스 바인딩은 CSS 클래스를 조건에 따라서 추가하거나 삭제할 수있음. [class.some-css-class]="some-condition를 스타일 하고자 하는 element에 넣으면 됨.

[class.selected] binding을 HeroesComponent 템플릿의 <button>에 추가해줌.
이제 현재 hero row의 hero("let hero of heroes"로 에서의 hero)가 selectedHero랑 같을때 앵귤러가 selected CSS 클래스를 버튼에 추가해줌. 다를 경우에는 앵귤러가 CSS클래스를 없앰.

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

Summary

  • ngFor를 사용해서 리스트를 보여줌
  • ngIf를 사용해서 조건에 따라 HTML 블럭을 포함하거나 제외할 수 있음
    • CSS 스타일 클래스를 class바인딩을 사용해서 사용할 수도 안할 수도 있음.

출처

0개의 댓글