Модуль 30

Оптимізація продуктивності Angular-додатків

Ось повноцінний урок, створений у стилі 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. 🧩 Підсумок

Отже, друзі, що ми маємо у сухому залишку?

  1. Change Detection — це офіціант. Зробіть його розумним, використовуйте OnPush.
  2. Шаблони — не місце для важкої логіки. Використовуйте Pipes.
  3. Списки — завжди використовуйте trackBy.
  4. Lazy Loading — не носіть у рюкзаку лижі влітку.

Тепер ви вмієте робити додатки, які не просто працюють, а "літають". Користувачі будуть любити вас, а батареї їхніх ноутбуків скажуть вам "дякую".

Що далі? Ми розібралися, як відображати дані швидко. Але як ефективно ними управляти, коли їх стає дуже багато? На наступному уроці ми пірнемо у світ State Management: NgRx та Signals. Це буде... реактивно!

А поки що — щасти вам із кодом!