Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Трубки в Angular (вбудовані та користувацькі)». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Pipes в Angular** перетворюють дані в шаблонах через метод `transform()` і повертають відформатований результат. Оригінальна властивість компонента не змінюється. ```typescript @Pipe({ name: 'truncate', standalone: true }) export class TruncatePipe implements PipeTransform { transform(value: string, limit = 50): string { return value?.length > limit ? value.substring(0, limit) + '...' : value; } } // Використання: {{ longText | truncate:20 }} ``` **Головне:** pipes чисті за замовчуванням і запускаються лише при зміні посилання на вхідні дані, що робить повторне форматування швидким.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Pipes в Angular** перетворюють дані в шаблонах: значення проходить через метод `transform()` і повертається відформатованим для відображення. Властивість компонента при цьому не змінюється. ## Теорія ### TL;DR - Pipe схожий на кавовий фільтр: "сирі" дані заходять, відформатовані виходять - Вбудовані pipes (`date`, `currency`, `uppercase`) покривають стандартне форматування без додаткового коду - Власні pipes - це класи з методом `transform()`, позначені декоратором `@Pipe` - Pipes об'єднуються в ланцюжок через `|`: `{{ value | pipe1 | pipe2 }}` - За замовчуванням pipe є чистим (pure): Angular запускає його лише при зміні посилання на вхідні дані ### Швидкий приклад ```html <!-- Вбудовані pipes --> {{ today | date:'short' }} <!-- 1/15/26, 2:30 PM --> {{ price | currency:'USD' }} <!-- $1,234.50 --> {{ 'hello world' | uppercase }} <!-- HELLO WORLD --> <!-- Ланцюжок: форматуємо валюту, потім обрізаємо рядок --> {{ price | currency:'USD' | slice:0:7 }} <!-- $1,234. --> ``` Pipe не торкається оригінального значення. Властивість компонента залишається незмінною, pipe впливає лише на те, що бачить шаблон. ### Відображення проти логіки Pipes форматують дані для відображення, а не для бізнес-логіки. Якщо перетворене значення потрібне в TypeScript-коді (для API, стану або обчислень), роби це в методі компонента чи сервісі. Форматування номера телефону для відображення - хороший кейс. Підрахунок річної зарплати з урахуванням податків через pipe - ні. ### Вбудовані pipes Angular постачається з pipes для найпоширеніших задач форматування: - `date` - форматує дати патернами: `'short'`, `'fullDate'`, `'dd/MM/yyyy'` - `currency` - форматує числа як гроші з підтримкою локалі та символів - `number` (DecimalPipe) - контролює кількість знаків після коми: `{{ 3.14159 | number:'1.2-3' }}` дає `3.142` - `uppercase`, `lowercase`, `titlecase` - перетворення регістру - `slice` - обрізає масиви або рядки: `{{ 'Angular' | slice:0:3 }}` дає `Ang` - `json` - серіалізує об'єкти для налагодження: `<pre>{{ user | json }}</pre>` - `async` - підписується на Observable або Promise і автоматично очищає підписку ### Власні pipes Щоб створити власний pipe, потрібно імплементувати `PipeTransform` і додати декоратор `@Pipe`: ```typescript import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'truncate', standalone: true }) export class TruncatePipe implements PipeTransform { transform(value: string, limit = 50, trail = '...'): string { if (!value) return ''; if (value.length <= limit) return value; return value.substring(0, limit) + trail; } } ``` ```html <p>{{ longText | truncate }}</p> <!-- 50 символів... --> <p>{{ longText | truncate:20 }}</p> <!-- 20 символів... --> <p>{{ longText | truncate:30:'→' }}</p> <!-- 30 символів→ --> ``` `name` в `@Pipe` - це те, що пишеш у шаблоні після `|`. Додаткові параметри через двокрапки в шаблоні стають наступними аргументами в `transform(value, arg1, arg2, ...)`. ### Чисті та нечисті pipes За замовчуванням кожен pipe є **чистим** (pure). Angular запускає його лише коли змінюється посилання на вхідні дані. Тобто `{{ items | sort }}` не перезапуститься, якщо додати елемент у той самий масив через `push`, бо посилання не змінилось. | | Чистий (за замовчуванням) | Нечистий (`pure: false`) | |---|---|---| | Запускається коли | Змінюється посилання на вхід | Кожен цикл виявлення змін | | Продуктивність | Ефективно | Може гальмувати | | Кейс | Безстанове форматування | Залежить від зовнішнього стану | Нечисті pipes потрібні, коли результат залежить від чогось поза вхідними даними, наприклад поточного часу. Але вони запускаються постійно. На списку з 500 елементів - 500 викликів `transform()` за цикл. Використовуй рідко, а для живих оновлень краще зроби сервіс з `interval()` через RxJS. ### AsyncPipe `async` підписується на Observable або Promise і автоматично відписується, коли компонент знищується. Без нього доводиться вручну керувати підписками, що легко забути і отримати витік пам'яті. На практиці це pipe, який в реальних Angular-проєктах зустрічається найчастіше. ```typescript @Component({ template: ` <div *ngIf='user$ | async as user'> {{ user.name }} </div> ` }) export class UserComponent { user$ = this.userService.getUser(1); constructor(private userService: UserService) {} } ``` Жодного `subscribe()`, жодного `unsubscribe()`, жодного `ngOnDestroy`. Pipe сам усе це вирішує. ### Типові помилки **Помилка 1: зробити безстановий pipe нечистим** ```typescript // Неправильно - запускається на кожному циклі виявлення змін @Pipe({ name: 'formatLabel', pure: false }) // Правильно - безстановому форматуванню нечистий pipe не потрібен @Pipe({ name: 'formatLabel' }) // pure: true за замовчуванням ``` На списку з 1000 елементів pipe із `pure: false` викликає `transform()` 1000 разів за цикл. Це вже не питання форматування, це питання продуктивності. **Помилка 2: мутація вхідних даних усередині pipe** ```typescript // Неправильно - сортуємо оригінальний масив transform(items: any[]): any[] { return items.sort((a, b) => a.name.localeCompare(b.name)); } // Правильно - повертаємо новий масив transform(items: any[]): any[] { return [...items].sort((a, b) => a.name.localeCompare(b.name)); } ``` Чисті pipes розраховують на незмінні вхідні дані. Сортування оригінального масиву непомітно змінює дані компонента і порушує роботу виявлення змін. **Помилка 3: забути зареєструвати власний pipe** ```typescript // Standalone компонент - імпортуємо pipe @Component({ imports: [TruncatePipe], template: `<p>{{ text | truncate }}</p>` }) // NgModule - декларуємо @NgModule({ declarations: [TruncatePipe] }) ``` Якщо pipe не зареєстровано, Angular кине помилку компіляції: `The pipe 'truncate' could not be found.` **Помилка 4: неправильний порядок pipes у ланцюжку** ```typescript // Неправильно - currency повертає рядок, а number очікує число {{ price | currency | number:'1.2-2' }} // Правильно - спочатку числове форматування, потім валюта {{ price | number:'1.2-2' | currency:'USD' }} ``` Вихід кожного pipe стає входом наступного. Відстежуй тип даних на кожному кроці ланцюжка. ### Де зустрічається - Дати в таблицях і картках: `{{ row.createdAt | date:'mediumDate' }}` - Відображення цін: `{{ item.price | currency:'EUR' }}` - Дані з Observable без ручних підписок: `{{ users$ | async }}` - Обрізання довгих описів у UI (власний `TruncatePipe`) - Форматування телефонів і артикулів в кількох шаблонах одразу (власний `PhoneFormatPipe`) - Налагодження стану під час розробки: `<pre>{{ data | json }}</pre>` Правило вибору: якщо те саме форматування зустрічається в 2 і більше шаблонах, створи pipe. Якщо це одноразове форматування, вистачить методу компонента. ### Питання на співбесіді **Q:** Яка різниця між чистим і нечистим pipe, і коли ти б обрав нечистий? **A:** Чистий pipe запускається лише при зміні посилання на вхідні дані. Нечистий - на кожному циклі виявлення змін. Нечистий потрібен тільки коли результат залежить від зовнішнього стану, що змінюється незалежно, наприклад поточного часу. Для всього безстанового форматування тримай pipe чистим. **Q:** Чи можна викликати pipe в TypeScript-коді, а не лише в шаблонах? **A:** Так. Можна створити екземпляр класу напряму: `new DatePipe('en-US').transform(date, 'short')`. Але це рідкість. Зазвичай трансформуєш дані в компоненті або сервісі, а pipe використовуєш лише для відображення. **Q:** Якщо застосувати власний pipe до 1000 елементів списку, Angular запустить `transform()` 1000 разів? **A:** При першому рендері - так. Потім чистий pipe кешує результат для кожного посилання і перезапускається лише для елементів, чиє посилання змінилось. Нечистий запускається всі 1000 разів за кожен цикл. Саме тому `pure: false` і великі списки погано поєднуються. **Q:** Як передати кілька параметрів у pipe? **A:** Розділяй двокрапками: `{{ value | date:'fullDate':'UTC' }}`. Вхідне значення - це перший аргумент `transform()`, а кожне значення після `:` передається як наступний аргумент за порядком. ## Приклади ### Базовий: вбудовані pipes для стандартного форматування ```html <!-- Дати --> <p>{{ today | date:'short' }}</p> <!-- 1/15/26, 2:30 PM --> <p>{{ today | date:'fullDate' }}</p> <!-- Thursday, January 15, 2026 --> <p>{{ today | date:'dd/MM/yyyy' }}</p> <!-- 15/01/2026 --> <!-- Валюта і числа --> <p>{{ price | currency }}</p> <!-- $1,234.50 --> <p>{{ price | currency:'EUR' }}</p> <!-- €1,234.50 --> <p>{{ 3.14159 | number:'1.2-3' }}</p> <!-- 3.142 --> <!-- Регістр тексту --> <p>{{ 'hello world' | uppercase }}</p> <!-- HELLO WORLD --> <p>{{ 'hello world' | titlecase }}</p> <!-- Hello World --> <!-- Ланцюжок pipes --> <p>{{ birthday | date:'fullDate' | uppercase }}</p> <!-- THURSDAY, JANUARY 15, 2026 --> ``` Ці pipes покривають більшість задач форматування в щоденній роботі з Angular. Без зайвої логіки в компоненті. ### Середній рівень: власний pipe для форматування номера телефону ```typescript import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'phoneFormat', standalone: true }) export class PhoneFormatPipe implements PipeTransform { transform(value: string): string { if (!value || value.length < 10) return value; const cleaned = value.replace(/\D/g, ''); const match = cleaned.match(/^(\d{3})(\d{3})(\d{4})$/); return match ? `(${match[1]}) ${match[2]}-${match[3]}` : value; } } ``` ```html <p>{{ '5551234567' | phoneFormat }}</p> <!-- (555) 123-4567 --> <p>{{ '' | phoneFormat }}</p> <!-- порожній рядок, без помилок --> ``` Перевірка на порожнє значення в рядку 5 - не випадкова. Pipe має обробляти некоректні вхідні дані без збоїв, бо реальні дані часто непередбачувані. При помилці форматування повертай оригінальне значення, а не кидай виняток.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.