Ось повноцінний урок, створений у стилі CS50, спеціально для тебе.
🎓 CS50: Оптимізація продуктивності Angular-додатків
(Вмикаємо світло в аудиторії, закочуємо рукави, на екрані — спінер завантаження)
1. 🔥 Вступ: Чому ваш додаток «гальмує»?
Уявіть, що ви прийшли в ресторан. Ви замовили лише склянку води. Але офіціант, перш ніж принести її, чомусь вирішує перевірити наявність серветок на кожному столі в закладі, перерахувати виделки на кухні й запитати у шеф-кухаря, чи свіжа риба.
Ви чекаєте. Ви дратуєтесь. Ви йдете в інший ресторан.
В Angular це відбувається постійно, якщо ви не знаєте правил гри.
Коли ваш додаток стає великим, він починає поводитися як цей дивний офіціант. Користувач натискає одну кнопку, а Angular "під капотом" перевіряє сотні компонентів: "А тут щось змінилося? А тут? А може тут?".
❓ Питання до вас: Як ви думаєте, скільки часу користувач готовий чекати на реакцію інтерфейсу, перш ніж вирішить, що додаток "завис"? (Спойлер: менше ніж 100 мілісекунд).
Сьогодні ми не просто будемо писати код. Ми будемо пришвидшувати час. Ми навчимо Angular бути лінивим там, де це треба, і швидким там, де це критично.
2. 🧠 Теоретична база: Що там «під капотом»?
Перш ніж лікувати, треба поставити діагноз. В Angular є два головних "ворога" продуктивності: 1. Розмір бандлу (Bundle Size) — скільки коду ми змушуємо користувача завантажити. 2. Зайві перемальовки (Change Detection) — скільки роботи робить браузер даремно.
Механізм Change Detection (Виявлення змін)
Уявіть дерево. Кожен листок — це компонент.
За замовчуванням (Default), коли відбувається будь-яка подія (клік, таймер, відповідь сервера), Angular починає бігати по всьому дереву зверху вниз і питати: "Ти змінився? Ти змінився?".
Це надійно, але дорого.
Стратегія OnPush
Ми можемо сказати Angular: "Слухай, не чіпай цей компонент, поки я тобі прямо не скажу, що вхідні дані (@Input) змінилися". Це називається ChangeDetectionStrategy.OnPush.
Lazy Loading (Ліниве завантаження)
Це аналогія з рюкзаком. Якщо ви йдете на пляж, навіщо вам лижі? Lazy Loading дозволяє завантажувати шматки коду (модулі) тільки тоді, коли користувач переходить на відповідну сторінку.
Що треба запам’ятати залізно:
* Angular за замовчуванням перевіряє все.
* Ми хочемо, щоб він перевіряв мінімум.
* Функції у шаблонах ({{ getFullName() }}) — це зло.
3. 🧪 Приклади: Від болю до насолоди
Приклад 1: Пастка з функцією в шаблоні
❓ Що ви очікуєте? У нас є простий метод, який просто виводить лог у консоль. Ми викликаємо його в HTML.
@Component({
template: `<h1>Hello, {{ getName() }}</h1>`
})
export class BadComponent {
getName() {
console.log('Виклик getName!');
return 'Student';
}
}
Ви думаєте, він викличеться один раз?
Реальність:
Спробуйте провести мишкою по екрану або натиснути кнопку в іншому місці додатку. Консоль вибухне:
Виклик getName!
Виклик getName!
Виклик getName!
Чому? Angular не знає, чи змінився результат функції, тому запускає її на кожен чих (кожен цикл Change Detection).
✅ Рішення: Використовуйте Pure Pipes або обчислюйте значення заздалегідь у змінну.
@Pipe({ name: 'userName', pure: true }) // Магія тут: pure: true
export class UserNamePipe implements PipeTransform {
transform(value: string): string {
console.log('Виклик Pipe!'); // Викличеться тільки якщо вхідні дані зміняться
return `Student ${value}`;
}
}
Приклад 2: *ngFor і втрата пам'яті
Уявіть список із 100 товарів. Ви додаєте 1 новий товар. За замовчуванням Angular знищить усі 100 елементів DOM і створить 101 заново. Це ж божевілля!
❓ Як це виправити? Нам треба дати кожному елементу "паспорт" (унікальний ID).
<!-- Погано -->
<div *ngFor="let item of items">...</div>
<!-- Добре -->
<div *ngFor="let item of items; trackBy: trackById">...</div>
trackById(index: number, item: any): number {
return item.id;
}
Тепер Angular подивиться на ID і скаже: "Ага, ці 100 такі самі, я їх не чіпаю. Просто домалюю один новий".
4. 🛠 Практична частина
Час забруднити руки кодом! Ось ваші завдання:
Завдання 1. "Детектив"
Відкрийте будь-який свій компонент. Знайдіть місце, где ви викликаєте функцію прямо в інтерполяції {{ myFunction() }}. Замініть це на Pipe або обчислюйте це значення в ngOnChanges / Signals.
Завдання 2. "Режим дзен"
Створіть компонент, який відображає картку користувача. Додайте в декоратор:
changeDetection: ChangeDetectionStrategy.OnPush.
Перевірте, чи оновлюється він, якщо ви просто зміните мутабельний об'єкт (наприклад user.name = 'Dave'). Чому ні? (Підказка: OnPush реагує на зміну посилання на об'єкт, а не його вмісту). Виправте це, передавши новий об'єкт (immutability).
Завдання 3. "Ледачий маршрут"
Розбийте ваш додаток на модулі. Зробіть так, щоб сторінка "Адмінка" (/admin) завантажувалася тільки тоді, коли ви на неї клікаєте. Перевірте вкладку "Network" у DevTools — ви маєте побачити новий JS-файл саме в момент переходу.
Завдання 4. Міні-кейс У вас є таблиця з 5000 рядків. Скрол гальмує. * Рішення: Не рендеріть те, що не видно. Використайте Angular CDK Virtual Scroll. Реалізуйте це.
Питання "А що, якщо..."
А що, якщо ви підписалися на Observable в ngOnInit і забули відписатися? Що станеться, якщо користувач 100 разів перейде на цю сторінку і назад? (Відповідь: Витік пам'яті. 100 підписок будуть жити вічно).
5. 💡 Мислення як у розробника
Як думає новачок?
"Код працює? Працює. Значить, готово!"
Як думає Senior Developer?
"Код працює, але скільки ресурсів він жере? Що буде, якщо даних стане в 100 разів більше? Чи не блокую я головний потік?"
Типові помилки:
1. Передчасна оптимізація. Не треба ставити OnPush скрізь підряд, якщо ви не розумієте, як це вплине на архітектуру.
2. Важкі обчислення в основному потоці. Якщо треба порахувати факторіал мільйона — винесіть це у Web Worker.
3. Ігнорування інструментів.
Порада з практики: Ніколи не оптимізуйте "на око". Відкрийте Chrome DevTools -> вкладка Performance. Запустіть запис, зробіть дію, зупиніть. Подивіться, де червоні графіки. Оптимізуйте саме це.
6. 🧩 Підсумок
Отже, друзі, що ми маємо у сухому залишку?
- Change Detection — це офіціант. Зробіть його розумним, використовуйте
OnPush. - Шаблони — не місце для важкої логіки. Використовуйте Pipes.
- Списки — завжди використовуйте
trackBy. - Lazy Loading — не носіть у рюкзаку лижі влітку.
Тепер ви вмієте робити додатки, які не просто працюють, а "літають". Користувачі будуть любити вас, а батареї їхніх ноутбуків скажуть вам "дякую".
Що далі? Ми розібралися, як відображати дані швидко. Але як ефективно ними управляти, коли їх стає дуже багато? На наступному уроці ми пірнемо у світ State Management: NgRx та Signals. Це буде... реактивно!
А поки що — щасти вам із кодом!