Оптимізація продуктивності вашого застосування з допомогою функцій Angular і не тільки

346

Від автора: сьогодні ми поговоримо про те, як оптимізувати продуктивність програми на Angular з допомогою ледачою завантаження, функцій TrackBy, OnPush, відключення виявлення змін і інших способів.

Використання OnPush

За замовчуванням Angular запускає механізм визначення змін на всіх компонентах кожен раз при зміні чого-небудь у додатку – від події click до отримання даних з ajax-запиту. (користувальницькі події, таймери, xhr, promise’и і т. д.)

Уявіть, наприклад, що у нас є компонент select.

@Component({
selector: ‘my-select’,
template: `
{{option.name}}
`
})
export class MySelectComponent {
@Input() options = [];
change() {}
}

Ми збираємося передати масив навичок в компонент select і встановити властивості як getters, щоб можна було стежити за тим, коли Angular перевіряє значення.

class Skill {
constructor( private _id, private _name ) {}
get id() {
console.log(‘Checking id’);
return this._id;
}
get name() {
console.log(‘Checking name’);
return this._name;
}
}
@Component({
template: `
Trigger change detection
`
})
export class AppComponent {
skills = [ new Skill(1, ‘JS’), new Skill(2, ‘CSS’), new Skill(3, ‘Angular’) ]
trigger() {}
}

При натисканні на кнопку Angular запустить цикл виявлення змін. Так як ми в режимі розробки, це буде відбуватися двічі за одне призначення. У нашому випадку обчислення наступні:

option.id * 2
+
option.name * 2
*
3 options
=
12

Оптимізація продуктивності вашого застосування з допомогою функцій Angular і не тільки

Angular дуже швидкий, однак з ростом вашого додатки Angular все складніше відслідковувати всі зміни.

Що якщо б ми могли допомогти Angular і показати, коли потрібно перевіряти компонент?

Ми можемо задати ChangeDetectionStrategy нашого компонента в ChangeDetectionStrategy.OnPush. Це говорить Angular, що компонент залежить лише від його Inputs і повинен перевірятися тільки в наступних випадках:

Змінюється посилання Input

У компоненті або його доньці відбулася подія

Ви явно запускаєте виявлення змін викликом detectChanges()/tick()/markForCheck()

@Component({
selector: ‘my-select’,
template: `

`,
changeDetection: ChangeDetectionStrategy.OnPush
})

Оптимізація продуктивності вашого застосування з допомогою функцій Angular і не тільки

Так як ні один з описаних вище умов не виконується, Angular не буде перевіряти компонент в поточному циклі визначення змін.

Використання TrackBy

Ми ще не закінчили. Якщо в якийсь момент нам потрібно буде змінити дані в колекції (this.skills), можливо, в результаті API-запиту, ми зіткнемося з проблемою, так як Angular не може відстежувати елементи в колекції, не знає про те, який з них вилучений або доданий.

Angular потрібно видалити всі елементи DOM, пов’язані з цими даними, і створити його заново. А це означає велику кількість маніпуляцій з DOM, особливо якщо велика колекція. А як ми знаємо, маніпуляції з DOM дорого обходяться.

Давайте подивимося все в дії.

export class AppComponent {
skills = [ … ]
ngOnInit() {
setTimeout(() => {
this.skills = [ …same skills as before, new Skill(4, ‘Typescript’) ]
}, 4000);
}
}

Оптимізація продуктивності вашого застосування з допомогою функцій Angular і не тільки

Є додати функцію trackBy, Angular зможе відстежувати елементи, які були додані або видалені з їх унікальним ідентифікатором, а також зможе створювати і знищувати те, що піддалося зміні.

Оптимізація продуктивності вашого застосування з допомогою функцій Angular і не тільки

Уникайте обчислюваних значень в шаблоні

Іноді необхідно трансформувати значення з сервера у що-небудь, що можна відобразити в UI. Наприклад:

@Component({
selector: ‘skills’,
template: `

{{skill.calcSomething(skill)}}

`
})
export class SkillsComponent {
calcSomething(skill) { … }
}

Проблема полягає в наступному: Angular потрібно перезапускати вашу функцію в кожному циклі виявлення змін. Якщо функція виконує щось серйозне, вона може сильно вплинути на продуктивність.

Якщо значення в процесі роботи динамічно не міняється, краще всього буде:

Використовувати чисті пайпи – Angular виконує чистий пайп тільки, коли виявляє чиста зміна значення.

Генерувати нове властивість і ставити його значення один раз, наприклад:

this.skills = this.skills.map(skill => ( { …skill, percentage: calcSomething(skill) } );

Відключення виявлення змін

Уявіть, що у вас є компонент, який залежить від постійно мінливих даних, кілька разів в секунду.

Оновлювати інтерфейс при зміні даних буде досить затратно. Краще перевіряти та оновлювати інтерфейс кожні Х секунд.

Це можна зробити, відокремивши детектор змін компонента, з допомогою локальної перевірки кожні х секунд.

@Component({
selector: ‘giant-list’,
template: `

  • Data {{d}}
    `,
    })
    class GiantList {
    constructor(private ref: ChangeDetectorRef, private dataProvider: DataProvider) {
    ref.detach();
    setInterval(() => {
    this.ref.detectChanges();
    }, 5000);
    }
    }

    Використання ледачою завантаження

    На мою думку, лінива завантаження – одна з найпотужніших функцій Angular і найменше використовуються.

    За замовчуванням Webpack виводить весь код програми в одну велику збірку. Лінива завантаження дає змогу оптимізувати час завантаження програми шляхом розбиття програми на функціональні модулі та завантаження на вимогу.

    Angular робить процес майже прозорим.

    {
    path: ‘admin’,
    loadChildren: ‘app/admin/admin.module#AdminModule’,
    }

    Ми навіть можемо відмовитися від завантаження цілих модулів при виконанні деяких умов. Наприклад, можна не завантажувати модуль admin, якщо користувач не адмін. (див. canLoad). От і все.