[Angular 마스터하기] Day 13 - 파이프, 데이터 변환의 마법
이제와서 시작하는 Angular 마스터하기 - Day 13 “파이프로 데이터를 원하는 형태로 변환하세요! 🔧”
오늘 배울 내용
- 내장 파이프 사용법
- 커스텀 파이프 만들기
- 파이프 체이닝
- Pure vs Impure 파이프
1. 내장 파이프
파이프는 템플릿에서 데이터를 표시용 형태로 바꾸는 도구입니다. 숫자, 날짜, 통화, JSON처럼 “값은 그대로 두고 화면에 보이는 모양만 바꾸고 싶을 때” 사용합니다.
중요한 원칙은 파이프가 비즈니스 로직을 숨기는 장소가 아니라는 점입니다. 할인 금액 계산, 권한 판단, API 응답 정규화처럼 앱의 규칙에 해당하는 코드는 서비스나 컴포넌트 로직에 두고, 파이프는 마지막 표시 형식 변환에 집중시키는 편이 좋습니다.
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
@Component({
selector: 'app-built-in-pipes',
standalone: true,
imports: [CommonModule],
template: `
<div class="pipes-demo">
<!-- DatePipe -->
<p>날짜: {{ today | date:'yyyy-MM-dd' }}</p>
<p>시간: {{ today | date:'HH:mm:ss' }}</p>
<!-- CurrencyPipe -->
<p>가격: {{ price | currency:'KRW':'symbol':'1.0-0' }}</p>
<!-- PercentPipe -->
<p>할인율: {{ discount | percent:'1.0-2' }}</p>
<!-- UpperCasePipe, LowerCasePipe -->
<p>대문자: {{ message | uppercase }}</p>
<p>소문자: {{ message | lowercase }}</p>
<!-- JsonPipe -->
<pre>{{ user | json }}</pre>
</div>
`
})
export class BuiltInPipesComponent {
today = new Date();
price = 123456;
discount = 0.15;
message = 'Hello Angular';
user = { name: '홍길동', age: 25 };
}
날짜와 통화 파이프는 로케일 설정의 영향을 받을 수 있습니다. 한국어 서비스라면 앱 전체의 locale 설정과 표시 형식이 실제 사용자 기대와 맞는지 확인하세요.
2. 커스텀 파이프
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
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate',
standalone: true
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit: number = 20, ellipsis: string = '...'): string {
if (!value) return '';
if (value.length <= limit) return value;
return value.substring(0, limit) + ellipsis;
}
}
// 사용
@Component({
imports: [TruncatePipe],
template: `
<p>{{ longText | truncate:50 }}</p>
`
})
export class AppComponent {
longText = '매우 긴 텍스트...';
}
Angular CLI로도 standalone pipe를 만들 수 있습니다.
1
ng generate pipe shared/pipes/truncate
최근 Angular CLI는 standalone pipe 생성을 기본 흐름으로 지원합니다. standalone pipe는 사용하는 컴포넌트의 imports에 직접 넣을 수 있어 작은 기능 단위로 재사용하기 좋습니다.
실용적인 커스텀 파이프
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
// 상대 시간 파이프
@Pipe({
name: 'timeAgo',
standalone: true
})
export class TimeAgoPipe implements PipeTransform {
transform(value: Date): string {
const now = new Date();
const seconds = Math.floor((now.getTime() - value.getTime()) / 1000);
if (seconds < 60) return `${seconds}초 전`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}분 전`;
if (seconds < 86400) return `${Math.floor(seconds / 3600)}시간 전`;
return `${Math.floor(seconds / 86400)}일 전`;
}
}
// 파일 크기 파이프
@Pipe({
name: 'fileSize',
standalone: true
})
export class FileSizePipe implements PipeTransform {
transform(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
}
커스텀 파이프는 테스트하기 쉽다는 장점도 있습니다. transform()은 입력과 출력이 명확한 함수이므로, 컴포넌트 렌더링 없이도 단위 테스트를 작성할 수 있습니다.
1
2
3
4
5
6
7
describe('FileSizePipe', () => {
const pipe = new FileSizePipe();
it('formats bytes as KB', () => {
expect(pipe.transform(2048)).toBe('2 KB');
});
});
3. 파이프 체이닝
1
2
3
4
5
6
7
8
@Component({
template: `
<!-- 여러 파이프 연결 -->
<p>{{ message | uppercase | truncate:10 }}</p>
<p>{{ price | currency:'KRW' | uppercase }}</p>
<p>{{ date | date:'short' | uppercase }}</p>
`
})
체이닝은 왼쪽에서 오른쪽으로 실행됩니다. 위 예시의 message | uppercase | truncate:10은 먼저 대문자로 바꾼 뒤 10글자로 자릅니다. 순서가 결과에 영향을 줄 수 있으니, 체이닝이 길어지면 읽기 쉬운지 다시 확인하세요.
4. Pure vs Impure 파이프
Angular pipe는 기본적으로 pure입니다. 즉 입력값이 바뀔 때만 transform()이 다시 실행됩니다. 대부분의 파이프는 이 기본값을 유지하는 것이 좋습니다.
1
2
3
4
5
6
7
8
9
@Pipe({
name: 'filterActive',
standalone: true
})
export class FilterActivePipe implements PipeTransform {
transform(users: User[]): User[] {
return users.filter(user => user.active);
}
}
위 파이프는 users 배열 참조가 바뀔 때 다시 계산됩니다. 배열 내부 객체를 직접 수정하면 Angular가 변화를 감지하지 못할 수 있으므로, 새 배열로 교체하는 방식이 더 안전합니다.
1
2
3
this.users.update(users =>
users.map(user => user.id === id ? { ...user, active: true } : user)
);
pure: false를 설정하면 change detection마다 파이프가 실행됩니다.
1
2
3
4
5
6
7
8
9
10
@Pipe({
name: 'now',
standalone: true,
pure: false
})
export class NowPipe implements PipeTransform {
transform(): string {
return new Date().toLocaleTimeString();
}
}
하지만 impure pipe는 성능 비용이 큽니다. 검색 필터, 정렬, 대량 리스트 변환을 impure pipe로 처리하면 화면이 느려질 수 있습니다. 가능하면 signal, computed, 서비스 로직에서 결과를 준비하고 템플릿에서는 가벼운 표시 변환만 하세요.
📝 정리
주요 내장 파이프
| 파이프 | 용도 | 예시 |
|---|---|---|
date | 날짜 형식 | date | date:'yyyy-MM-dd' |
currency | 통화 형식 | price | currency:'KRW' |
percent | 퍼센트 | rate | percent |
json | JSON 출력 | obj | json |
파이프 선택 기준
| 상황 | 추천 |
|---|---|
| 날짜/통화/퍼센트 표시 | 내장 파이프 |
| 짧은 표시 형식 변환 | 커스텀 pure pipe |
| 계산 비용이 큰 필터/정렬 | 컴포넌트의 computed/service |
| 매 렌더링마다 바뀌는 값 | pipe보다 명시적 상태 관리 |
체크리스트
- 내장 파이프를 사용할 수 있나요?
- 커스텀 파이프를 만들 수 있나요?
- 파이프를 체이닝할 수 있나요?
- pure pipe와 impure pipe의 차이를 설명할 수 있나요?
- 표시 변환과 비즈니스 로직을 구분할 수 있나요?
📚 다음 학습
“파이프로 데이터를 멋지게 변환하세요!” 🔧
![[Angular 마스터하기] Day 13 - 파이프, 데이터 변환의 마법](/assets/img/posts/angular/angular-day-13.png)