포스트

[Angular 마스터하기] Day 2 - 컴포넌트 기초, 나만의 첫 컴포넌트

[Angular 마스터하기] Day 2 - 컴포넌트 기초, 나만의 첫 컴포넌트

이제와서 시작하는 Angular 마스터하기 - Day 2 “컴포넌트는 레고 블록입니다. 하나씩 만들어서 조립하면 멋진 앱이 완성돼요! 🧱”

오늘 배울 내용

  • 컴포넌트가 무엇인지 이해하기
  • Standalone Component 만들기 (최신 방식)
  • 템플릿, 스타일, 로직의 3요소
  • Angular CLI로 컴포넌트 자동 생성
  • 실습: 프로필 카드 컴포넌트

1. 컴포넌트가 뭘까요?

웹 페이지의 레고 블록

컴포넌트는 재사용 가능한 UI 조각입니다.

실생활 예시:

1
2
3
4
5
6
7
8
9
유튜브 화면을 생각해보세요
┌─────────────────────────────┐
│  [헤더 컴포넌트]             │
├─────────────────────────────┤
│ [동영상 카드] [동영상 카드]  │
│ [동영상 카드] [동영상 카드]  │  ← 같은 컴포넌트 반복!
├─────────────────────────────┤
│  [푸터 컴포넌트]             │
└─────────────────────────────┘
graph TD
    A[웹 애플리케이션] --> B[헤더 컴포넌트]
    A --> C[메인 컴포넌트]
    A --> D[푸터 컴포넌트]
    C --> E[프로필 카드 컴포넌트]
    C --> F[게시글 컴포넌트]
    C --> G[댓글 컴포넌트]

    style A fill:#dd0031,stroke:#fff,color:#fff
    style C fill:#c3002f,stroke:#fff,color:#fff

컴포넌트의 3요소

모든 Angular 컴포넌트는 3가지로 구성됩니다:

요소 역할 파일
🎨 템플릿 (Template) 화면 구조 (HTML) .component.html
💅 스타일 (Style) 디자인 (CSS) .component.css
🧠 클래스 (Class) 로직 (TypeScript) .component.ts
1
2
3
4
5
6
7
8
9
10
// 컴포넌트 = 템플릿 + 스타일 + 로직
@Component({
  selector: 'app-profile',     // HTML에서 사용할 태그명
  templateUrl: './profile.component.html',  // 템플릿
  styleUrl: './profile.component.css'       // 스타일
})
export class ProfileComponent {
  // 로직 (데이터, 함수)
  name = '홍길동';
}

2. 첫 컴포넌트 만들기

수동으로 만들기 (이해를 위해)

먼저 손으로 직접 만들어보면서 구조를 이해해봅시다!

Step 1: 폴더 생성

1
2
3
4
5
src/app/
  └── greeting/
      ├── greeting.component.ts
      ├── greeting.component.html
      └── greeting.component.css

Step 2: greeting.component.ts 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component } from '@angular/core';

@Component({
  selector: 'app-greeting',        // <app-greeting></app-greeting>으로 사용
  standalone: true,                // ⭐ 최신 방식! (모듈 불필요)
  templateUrl: './greeting.component.html',
  styleUrl: './greeting.component.css'
})
export class GreetingComponent {
  // 데이터
  username = '방문자';
  currentTime = new Date().toLocaleTimeString('ko-KR');

  // 메서드
  sayHello() {
    alert(`안녕하세요, ${this.username}님!`);
  }
}

Step 3: greeting.component.html 작성

1
2
3
4
5
<div class="greeting-card">
  <h2>👋 환영합니다!</h2>
  <p>{{ username }}님, 현재 시각은 {{ currentTime }}입니다.</p>
  <button (click)="sayHello()">인사하기</button>
</div>

Step 4: greeting.component.css 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.greeting-card {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 30px;
  border-radius: 15px;
  text-align: center;
  box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}

.greeting-card h2 {
  font-size: 2em;
  margin-bottom: 15px;
}

.greeting-card button {
  background: white;
  color: #667eea;
  border: none;
  padding: 12px 30px;
  border-radius: 25px;
  font-size: 1em;
  font-weight: bold;
  cursor: pointer;
  transition: transform 0.2s;
}

.greeting-card button:hover {
  transform: scale(1.05);
}

Step 5: app.component.ts에 등록

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Component } from '@angular/core';
import { GreetingComponent } from './greeting/greeting.component';  // 임포트!

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [GreetingComponent],  // ⭐ 여기에 추가!
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  title = 'my-first-app';
}

Step 6: app.component.html에서 사용

1
2
3
4
5
6
<div style="padding: 50px;">
  <h1>{{ title }}</h1>

  <!-- 우리가 만든 컴포넌트 사용! -->
  <app-greeting></app-greeting>
</div>

결과 확인: http://localhost:4200에서 확인! 🎉


3. CLI로 자동 생성하기 (실전 방식)

수동으로 만드니까 번거롭죠? Angular CLI가 자동으로 해줍니다!

명령어 한 줄이면 끝

1
2
3
4
ng generate component profile-card

# 축약형
ng g c profile-card

자동으로 생성되는 파일:

1
2
3
4
CREATE src/app/profile-card/profile-card.component.css
CREATE src/app/profile-card/profile-card.component.html
CREATE src/app/profile-card/profile-card.component.spec.ts  # 테스트 파일
CREATE src/app/profile-card/profile-card.component.ts

생성된 코드:

1
2
3
4
5
6
7
8
9
10
11
12
import { Component } from '@angular/core';

@Component({
  selector: 'app-profile-card',
  standalone: true,
  imports: [],
  templateUrl: './profile-card.component.html',
  styleUrl: './profile-card.component.css'
})
export class ProfileCardComponent {
  // 여기에 코드 작성!
}

💡 꿀팁: CLI가 자동으로 standalone: true를 설정해줍니다!


4. 실전 예제: 프로필 카드 만들기

이제 진짜 쓸모있는 컴포넌트를 만들어봅시다!

요구사항

  • 프로필 사진
  • 이름, 직업
  • 좋아요 버튼
  • 좋아요 개수 표시

profile-card.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { Component } from '@angular/core';

@Component({
  selector: 'app-profile-card',
  standalone: true,
  imports: [],
  templateUrl: './profile-card.component.html',
  styleUrl: './profile-card.component.css'
})
export class ProfileCardComponent {
  // 프로필 데이터
  name = '김개발';
  job = 'Frontend Developer';
  profileImage = 'https://i.pravatar.cc/150?img=12';
  likes = 42;
  isLiked = false;

  // 좋아요 토글
  toggleLike() {
    if (this.isLiked) {
      this.likes--;
      this.isLiked = false;
    } else {
      this.likes++;
      this.isLiked = true;
    }
  }
}

profile-card.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="profile-card">
  <!-- 프로필 이미지 -->
  <img [src]="profileImage" [alt]="name" class="profile-image">

  <!-- 정보 -->
  <h3 class="name">{{ name }}</h3>
  <p class="job">{{ job }}</p>

  <!-- 좋아요 버튼 -->
  <div class="actions">
    <button
      (click)="toggleLike()"
      [class.liked]="isLiked"
      class="like-button">
      {{ isLiked ? '❤️' : '🤍' }} {{ likes }}
    </button>
  </div>
</div>

profile-card.component.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
.profile-card {
  max-width: 300px;
  background: white;
  border-radius: 20px;
  padding: 30px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
  text-align: center;
  transition: transform 0.3s;
}

.profile-card:hover {
  transform: translateY(-10px);
}

.profile-image {
  width: 150px;
  height: 150px;
  border-radius: 50%;
  object-fit: cover;
  border: 5px solid #dd0031;
  margin-bottom: 20px;
}

.name {
  font-size: 1.8em;
  margin: 10px 0;
  color: #333;
}

.job {
  color: #666;
  font-size: 1.1em;
  margin-bottom: 20px;
}

.like-button {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border: none;
  padding: 12px 30px;
  border-radius: 25px;
  font-size: 1.1em;
  cursor: pointer;
  transition: all 0.3s;
}

.like-button:hover {
  transform: scale(1.05);
  box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
}

.like-button.liked {
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}

app.component.ts에 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Component } from '@angular/core';
import { ProfileCardComponent } from './profile-card/profile-card.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ProfileCardComponent],  // 추가!
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  title = '프로필 카드 갤러리';
}

app.component.html에서 사용

1
2
3
4
5
6
7
8
9
<div class="container">
  <h1 style="text-align: center; color: #333;">{{ title }}</h1>

  <div class="card-grid">
    <app-profile-card></app-profile-card>
    <app-profile-card></app-profile-card>
    <app-profile-card></app-profile-card>
  </div>
</div>

app.component.css

1
2
3
4
5
6
7
8
9
10
11
12
.container {
  padding: 50px;
  background: #f5f5f5;
  min-height: 100vh;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 30px;
  margin-top: 40px;
}

5. 컴포넌트 이해하기

Selector의 비밀

1
2
3
4
@Component({
  selector: 'app-profile-card',  // ← 이게 뭘까?
  // ...
})

HTML에서 이렇게 사용:

1
<app-profile-card></app-profile-card>

💡 명명 규칙:

  • 항상 app-으로 시작 (프로젝트 접두사)
  • 케밥-케이스 사용 (kebab-case)
  • 의미있는 이름 사용

예시:

  • app-user-profile
  • app-product-card
  • profile (너무 짧음)
  • appProfile (케밥-케이스 아님)

Standalone Components (⭐ 중요!)

과거 방식 (NgModule):

1
2
3
4
5
6
// 복잡했어요...
@NgModule({
  declarations: [ProfileCardComponent],
  imports: [CommonModule],
  exports: [ProfileCardComponent]
})

최신 방식 (Standalone):

1
2
3
4
5
// 간단해요!
@Component({
  standalone: true,  // ← 이것만 추가!
  imports: []        // 필요한 것만 여기에
})

장점:

  • ✅ 더 간단한 구조
  • ✅ 더 빠른 빌드
  • ✅ 더 작은 번들 크기
  • ✅ 더 쉬운 테스트

🧪 직접 해보기

실습 1: 상품 카드 컴포넌트

미션: 상품을 표시하는 카드를 만드세요!

요구사항:

  • 상품 이미지
  • 상품명
  • 가격
  • 장바구니 담기 버튼

시작하기:

1
ng g c product-card

힌트:

1
2
3
4
5
6
7
8
9
export class ProductCardComponent {
  productName = '무선 이어폰';
  price = 89000;
  imageUrl = 'https://picsum.photos/200/200';

  addToCart() {
    alert(`${this.productName}을(를) 장바구니에 담았습니다!`);
  }
}

실습 2: 여러 개 카드 표시하기

app.component.html:

1
2
3
4
5
6
<div class="product-grid">
  <app-product-card></app-product-card>
  <app-product-card></app-product-card>
  <app-product-card></app-product-card>
  <app-product-card></app-product-card>
</div>

app.component.css:

1
2
3
4
5
6
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px;
  padding: 20px;
}

💡 자주 하는 실수

❌ 실수 1: imports에 추가 안 함

1
2
3
4
5
6
// 에러 발생!
@Component({
  standalone: true,
  imports: [],  // ← ProfileCardComponent를 안 넣음
  // ...
})

에러 메시지:

1
'app-profile-card' is not a known element

해결:

1
imports: [ProfileCardComponent]  // ✅ 추가!

❌ 실수 2: selector 오타

1
selector: 'app-profle-card',  // ← 'profile' 오타!
1
<app-profile-card></app-profile-card>  <!-- 작동 안 함! -->

❌ 실수 3: 파일 경로 틀림

1
templateUrl: './profile.component.html',  // ← 파일명 확인!

✅ 디버깅 팁

컴포넌트가 안 보일 때 체크리스트:

  1. 컴포넌트를 import 했나요?
  2. imports 배열에 추가했나요?
  3. selector가 맞나요?
  4. 파일 경로가 맞나요?
  5. 개발 서버가 실행 중인가요?

📝 정리

핵심 개념

mindmap
  root((컴포넌트))
    구조
      템플릿 HTML
      스타일 CSS
      클래스 TS
    생성 방법
      수동 생성
      CLI 자동 생성
    사용 방법
      import
      imports 배열
      selector 태그
    특징
      재사용 가능
      독립적
      조합 가능

오늘 배운 명령어

1
2
3
4
5
6
7
8
# 컴포넌트 생성
ng generate component 컴포넌트명
ng g c 컴포넌트명

# 예시
ng g c profile-card
ng g c product-card
ng g c user-list

체크리스트

  • 컴포넌트의 개념을 이해했나요?
  • CLI로 컴포넌트를 생성했나요?
  • 프로필 카드를 만들어봤나요?
  • 여러 개 카드를 표시해봤나요?

📚 다음 학습

다음 시간에는 템플릿 문법을 배웁니다!

지금은 간단한 {{ }} 문법만 사용했는데요, Angular 템플릿에는 더 강력한 기능이 많습니다:


💬 마무리하며

“컴포넌트는 레고 블록입니다. 작은 조각들을 모아 멋진 작품을 만드세요! 🧱”

오늘은 컴포넌트의 기초를 배웠습니다. 어떤가요? 생각보다 어렵지 않죠?

내일은 템플릿에서 데이터를 더 멋지게 표시하는 방법을 배웁니다!

실습하다가 막히는 부분이 있으면 댓글로 질문해주세요! 🙋‍♂️


“천 리 길도 한 걸음부터. 오늘도 한 걸음 전진했습니다!” 🚀

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.