10. Pipes
🔧 Angular Pipes — трансформация данных в шаблонах
Заголовок раздела «🔧 Angular Pipes — трансформация данных в шаблонах»Pipes (пайпы) — это фильтры для данных в шаблоне. Они преобразуют значения прямо в {{ }} интерполяции и property-binding, не изменяя оригинальные данные. Думай о них как о конвейере: данные входят в одном виде, выходят в другом.
🤔 Зачем нужны Pipes?
Заголовок раздела «🤔 Зачем нужны Pipes?»Без пайпов каждый раз нужно писать методы в компоненте или форматировать данные заранее. Пайпы делают шаблон декларативным и читаемым:
// ❌ Без pipes — загромождённый компонент@Component({ template: `<p>{{ formatDate(user.birthday) }}</p>`})export class UserComponent { formatDate(d: Date): string { return d.toLocaleDateString('ru-RU', { year: 'numeric', month: 'long', day: 'numeric' }); }}
// ✅ С pipes — чистый шаблон@Component({ template: `<p>{{ user.birthday | date:'d MMMM yyyy':'':'ru' }}</p>`})export class UserComponent {}📐 Синтаксис Pipes
Заголовок раздела «📐 Синтаксис Pipes»<!-- Базовый синтаксис: {{ значение | имяПайпа }} -->{{ 'angular' | uppercase }} <!-- ANGULAR -->
<!-- С аргументами: | имяПайпа:арг1:арг2 -->{{ price | currency:'RUB':'symbol':'1.2-2' }} <!-- ₽1,000.00 -->
<!-- Цепочка пайпов: применяются слева направо -->{{ title | trim | titlecase | slice:0:20 }}
<!-- В property binding — без {{ }} --><img [src]="imageUrl | safeUrl"><div [title]="description | slice:0:50">...</div>
<!-- В *ngFor -->@for (item of items | slice:0:5 | keyvalue; track item.key) { {{ item.key }}: {{ item.value }}}📦 Встроенные Pipes Angular
Заголовок раздела «📦 Встроенные Pipes Angular»🔤 Текстовые Pipes
Заголовок раздела «🔤 Текстовые Pipes»@Component({ template: ` <p>{{ 'hello world' | uppercase }}</p> <!-- HELLO WORLD --> <p>{{ 'HELLO WORLD' | lowercase }}</p> <!-- hello world --> <p>{{ 'hello world' | titlecase }}</p> <!-- Hello World --> <p>{{ longText | slice:0:100 }}...</p> <!-- первые 100 символов --> <p>{{ obj | json }}</p> <!-- {"key":"value"} — для отладки! --> `})export class TextPipesComponent { longText = 'Это очень длинный текст который нужно обрезать для превью'; obj = { name: 'Angular', version: 17, stable: true };}🔢 Числовые Pipes
Заголовок раздела «🔢 Числовые Pipes»@Component({ template: ` <!-- DecimalPipe: {минЦелых}.{минДробных}-{максДробных} --> {{ 3.14159 | number:'1.2-3' }} <!-- 3.142 --> {{ 1234567 | number:'1.0-0' }} <!-- 1,234,567 --> {{ 0.001 | number:'1.4-6' }} <!-- 0.001000 -->
<!-- PercentPipe --> {{ 0.75 | percent }} <!-- 75% --> {{ 0.1234 | percent:'1.1-2' }} <!-- 12.34% --> {{ 1.5 | percent:'1.0-0' }} <!-- 150% -->
<!-- CurrencyPipe: (значение | currency:код:символ:формат:локаль) --> {{ 1000 | currency:'USD' }} <!-- $1,000.00 --> {{ 1000 | currency:'EUR':'symbol' }} <!-- €1,000.00 --> {{ 1000 | currency:'RUB':'code' }} <!-- RUB 1,000.00 --> {{ 1000 | currency:'RUB':'symbol':'1.0-0':'ru' }} <!-- 1 000 ₽ --> `})export class NumberPipesComponent {}📅 DatePipe — Форматирование дат
Заголовок раздела «📅 DatePipe — Форматирование дат»@Component({ template: ` <!-- Предустановленные форматы --> {{ today | date:'short' }} <!-- 1/15/24, 3:45 PM --> {{ today | date:'medium' }} <!-- Jan 15, 2024, 3:45:00 PM --> {{ today | date:'long' }} <!-- January 15, 2024 at 3:45:00 PM GMT+3 --> {{ today | date:'full' }} <!-- Monday, January 15, 2024 at 3:45:00 PM -->
<!-- Только дата / только время --> {{ today | date:'shortDate' }} <!-- 1/15/24 --> {{ today | date:'shortTime' }} <!-- 3:45 PM --> {{ today | date:'mediumDate' }} <!-- Jan 15, 2024 -->
<!-- Кастомный формат --> {{ today | date:'dd.MM.yyyy' }} <!-- 15.01.2024 --> {{ today | date:'HH:mm:ss' }} <!-- 15:45:00 --> {{ today | date:'d MMMM yyyy' }} <!-- 15 января 2024 (нужна локаль ru) --> {{ today | date:'EEEE' }} <!-- Monday (день недели) -->
<!-- С timezone и локалью --> {{ today | date:'medium':'UTC': 'ru' }} {{ today | date:'d MMMM yyyy':'':'ru' }}
<!-- Строка ISO тоже работает! --> {{ '2024-03-15T10:30:00' | date:'dd.MM.yyyy HH:mm' }} `})export class DatePipeComponent { today = new Date();}💡 Для русского форматирования нужно зарегистрировать локаль в
main.ts:import { registerLocaleData } from '@angular/common';import localeRu from '@angular/common/locales/ru';registerLocaleData(localeRu);
🔄 AsyncPipe — работа с Observable и Promise
Заголовок раздела «🔄 AsyncPipe — работа с Observable и Promise»AsyncPipe — самый мощный встроенный пайп. Он автоматически подписывается на Observable/Promise и отписывается при уничтожении компонента, предотвращая утечки памяти.
@Component({ template: ` <!-- Без AsyncPipe — ручная подписка + unsubscribe --> <p>{{ userName }}</p>
<!-- С AsyncPipe — автоматически! --> <p>{{ userName$ | async }}</p>
<!-- AsyncPipe с *ngIf для получения значения в переменную --> <div *ngIf="user$ | async as user"> <h2>{{ user.name }}</h2> <p>{{ user.email }}</p> </div>
<!-- Новый синтаксис Angular 17 --> @if (user$ | async; as user) { <h2>{{ user.name }}</h2> }
<!-- AsyncPipe со списком --> <ul> @for (item of items$ | async; track item.id) { <li>{{ item.name }}</li> } </ul>
<!-- Комбинирование с другими пайпами --> {{ (count$ | async) ?? 0 | number:'1.0-0' }} `, // Важно! С AsyncPipe можно использовать OnPush — максимальная производительность changeDetection: ChangeDetectionStrategy.OnPush})export class AsyncPipeComponent implements OnInit { userName$!: Observable<string>; user$!: Observable<User>; items$!: Observable<Item[]>; count$!: Observable<number>;
constructor(private userService: UserService) {}
ngOnInit(): void { this.user$ = this.userService.getUser(1); this.items$ = this.userService.getItems().pipe( map(items => items.filter(i => i.active)) ); // При уничтожении компонента AsyncPipe сам вызовет unsubscribe()! ✨ }}// ❌ Без AsyncPipe — риск утечки памяти!@Component({})export class BadComponent implements OnInit, OnDestroy { user: User | null = null; private destroy$ = new Subject<void>();
ngOnInit(): void { this.userService.getUser(1).pipe( takeUntil(this.destroy$) ).subscribe(user => this.user = user); }
ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); }}🗝️ KeyValuePipe — итерация по объектам
Заголовок раздела «🗝️ KeyValuePipe — итерация по объектам»@Component({ template: ` <!-- Объект --> <dl> @for (entry of userInfo | keyvalue; track entry.key) { <dt>{{ entry.key }}</dt> <dd>{{ entry.value }}</dd> } </dl>
<!-- Map --> @for (entry of configMap | keyvalue; track entry.key) { {{ entry.key }}: {{ entry.value }} }
<!-- Кастомная сортировка: 0 = без сортировки --> @for (entry of obj | keyvalue:originalOrder; track entry.key) { {{ entry.key }} = {{ entry.value }} } `})export class KeyValueComponent { userInfo = { name: 'Яша', age: 10, city: 'Москва', language: 'TypeScript' };
configMap = new Map([ ['theme', 'dark'], ['lang', 'ru'], ['version', '17'] ]);
// Сохранить оригинальный порядок ключей originalOrder = (): number => 0;}⚡ Pure vs Impure Pipes
Заголовок раздела «⚡ Pure vs Impure Pipes»Это критически важное различие для производительности!
| Характеристика | Pure Pipe | Impure Pipe |
|---|---|---|
| Вызывается когда | Только при изменении входного значения (по ссылке) | При КАЖДОМ цикле change detection |
| Производительность | ✅ Высокая | ⚠️ Низкая |
| Мутация входных данных | Не видит | Видит |
pure: true (default) | ✅ | ❌ |
| Примеры | date, currency, uppercase | async |
// ✅ Pure pipe (по умолчанию) — вызывается только при изменении ссылки@Pipe({ name: 'filterItems', pure: true }) // pure: true — это дефолтexport class FilterPipe implements PipeTransform { transform(items: Item[], search: string): Item[] { console.log('pipe called'); // Будет вызван редко — только при новом массиве return items.filter(i => i.name.includes(search)); }}
// Проблема с pure pipe при мутации массива:addItem() { this.items.push(newItem); // ❌ Ссылка та же — pipe НЕ видит изменение! this.items = [...this.items, newItem]; // ✅ Новая ссылка — pipe вызовется}
// ⚠️ Impure pipe — вызывается при каждой проверке изменений@Pipe({ name: 'filterImpure', pure: false })export class FilterImpurePipe implements PipeTransform { transform(items: Item[], search: string): Item[] { console.log('pipe called'); // Вызывается постоянно — осторожно! return items.filter(i => i.name.includes(search)); }}🎯 Правило: Используй
pure: falseтолько когда нужно реагировать на мутации объектов/массивов. В большинстве случаев достаточно pure pipe + иммутабельные обновления.
🔗 Цепочки Pipes
Заголовок раздела «🔗 Цепочки Pipes»<!-- Несколько пайпов применяются слева направо -->{{ product.name | uppercase | slice:0:15 }}
<!-- Дата + локаль -->{{ createdAt | date:'d MMMM' | uppercase }} <!-- 15 ЯНВАРЯ -->
<!-- Число отформатировать как валюту и обрезать -->{{ (price * rate) | number:'1.2-2' }}
<!-- AsyncPipe с трансформацией -->{{ (price$ | async) | currency:'RUB':'symbol':'1.0-0':'ru' }}
<!-- Null safety: если значение null/undefined, показываем дефолт -->{{ (user$ | async)?.name | titlecase }}{{ nullableValue ?? 'N/A' | uppercase }} <!-- оператор ?? работает до pipe -->📊 Быстрая шпаргалка по встроенным Pipes
Заголовок раздела «📊 Быстрая шпаргалка по встроенным Pipes»| Pipe | Синтаксис | Пример результата |
|---|---|---|
uppercase | 'hello' | uppercase | HELLO |
lowercase | 'HELLO' | lowercase | hello |
titlecase | 'hello world' | titlecase | Hello World |
slice | 'abcde' | slice:1:3 | bc |
json | {a:1} | json | {"a": 1} |
number | 3.14 | number:'1.1-2' | 3.14 |
percent | 0.75 | percent | 75% |
currency | 100 | currency:'USD' | $100.00 |
date | date | date:'shortDate' | 1/15/24 |
async | obs$ | async | (значение) |
keyvalue | obj | keyvalue | [{key, value}] |
🧩 Подключение Pipes в компоненте
Заголовок раздела «🧩 Подключение Pipes в компоненте»// Standalone компонент (Angular 14+) — импортируем нужные pipes@Component({ standalone: true, imports: [ DatePipe, CurrencyPipe, DecimalPipe, UpperCasePipe, AsyncPipe, JsonPipe, SlicePipe, KeyValuePipe, PercentPipe, TitleCasePipe, LowerCasePipe, ], template: `{{ today | date:'mediumDate' }}`})export class MyComponent { today = new Date();}
// Или импортируй весь CommonModule (NgModules подход)@NgModule({ imports: [CommonModule]})export class MyModule {}