Оптимізація Angular форм за допомогою сигналів для кращої продуктивності

Angular Signals надають реактивний та ефективний спосіб керування станом і обчисленими значеннями у вашому додатку. У цьому блозі ми продемонструємо, як Signals можуть оптимізувати продуктивність при роботі з Angular Reactive Forms, запобігаючи постійним переоцінкам, викликаним традиційними геттерами.

Проблема з традиційними геттерами в Angular формах

При роботі зі складними формами, особливо з полями для адреси, використання геттера для обчислення комбінованих значень адреси може призвести до частих перерахунків, навіть якщо основні дані не змінилися. Це може спричинити непотрібне навантаження на продуктивність.

Приклад: Форма адреси з Signals

Ми створимо форму з наступними полями:

  • Ім'я
  • BIC
  • Код клірингу
  • Рахунок
  • Назва будівлі
  • Номер будівлі
  • Адреса вулиці
  • Поштовий ящик
  • Місто
  • Штат
  • Країна
  • Поштовий індекс

1.

Визначення форми

import { Component } from '@angular/core';  
import { Signal, computed, inject, Injector } from '@angular/core';  
import { FormBuilder, FormGroup } from '@angular/forms';  
import { toSignal } from '@angular/core/rxjs-interop';  
import { of } from 'rxjs';  
@Component({  
 selector: 'my-app',  
 templateUrl: './app.component.html',  
 styleUrls: ['./app.component.scss'],  
})  
export class AppComponent {  
 form!: FormGroup;  

 private injector = inject(Injector);  

 // Signals для полів адреси  
 buildingName!: Signal | undefined;  
 buildingNumber!: Signal | undefined;  
 streetAddress!: Signal | undefined;  
 poBox!: Signal | undefined;  
 city!: Signal | undefined;  
 state!: Signal | undefined;  
 country!: Signal | undefined;  
 postalCode!: Signal | undefined;  

 exampleData = {  
 name: 'John Doe',  
 bic: 'ABCDEF12',  
 clearingCode: '12345',  
 account: '9876543210',  

buildingName: 'Sunrise Tower',  
 buildingNumber: '45A',  
 streetAddress: 'Elm Street',  
 poBox: 'PO1234',  
 city: 'Metropolis',  
 state: 'New York',  
 country: 'USA',  
 postalCode: '10001',  
 };  

 getterCallCount = 0;  
 signalCallCount = 0;  

 constructor(private fb: FormBuilder) {  
 this.form = this.fb.group({  
 name: '',  
 bic: '',  
 clearingCode: '',  
 account: '',  
 buildingName: '',  
 buildingNumber: '',  
 streetAddress: '',  
 poBox: '',  
 city: '',  
 state: '',  
 country: '',  
 postalCode: '',  
 });  

 // Ініціалізація Signals після створення форми з ?? та Injector  
 this.buildingName = toSignal(  
 this.form.controls['buildingName'].valueChanges ?? of(undefined),  
 { injector: this.injector }  
 );  
 this.buildingNumber = toSignal(  
 this.form.controls['buildingNumber'].valueChanges ?? of(undefined),  
 { injector: this.injector }  
 );  
 this.streetAddress = toSignal(  
 this.form.controls['streetAddress'].valueChanges ?? of(undefined),  

{ injector: this.injector }  
 );  
 this.poBox = toSignal(  
 this.form.controls['poBox'].valueChanges ?? of(undefined),  
 { injector: this.injector }  
 );  
 this.city = toSignal(  
 this.form.controls['city'].valueChanges ?? of(undefined),  
 { injector: this.injector }  
 );  
 this.state = toSignal(  
 this.form.controls['state'].valueChanges ?? of(undefined),  
 { injector: this.injector }  
 );  
 this.country = toSignal(  
 this.form.controls['country'].valueChanges ?? of(undefined),  
 { injector: this.injector }  
 );  
 this.postalCode = toSignal(  
 this.form.controls['postalCode'].valueChanges ?? of(undefined),  
 { injector: this.injector }  
 );  

 this.form.patchValue(this.exampleData);  
 }  

 // Підхід за допомогою Getter  
 get fullAddressGetter(): string {  
 this.getterCallCount++;  
 return [  
 this.form.value.buildingName,  
 this.form.value.buildingNumber,  
 this.form.value.streetAddress,  
 this.form.value.poBox,  
 this.form.value.city,  

this.form.value.state,  
 this.form.value.country,  
 this.form.value.postalCode,  
 ]  
 .filter(Boolean)  
 .join(', ');  
 }  

 // Підхід за допомогою обчислювального сигналу (computed Signal)  
 fullAddressSignal = computed(() => {  
 this.signalCallCount++;  
 return [  
 this.buildingName?.(),  
 this.buildingNumber?.(),  
 this.streetAddress?.(),  
 this.poBox?.(),  
 this.city?.(),  
 this.state?.(),  
 this.country?.(),  
 this.postalCode?.(),  
 ]  
 .filter(Boolean)  
 .join(', ');  
 });  
}

Приклад даних
{{ exampleData | json }}
Адреса (Getter): {{ fullAddressGetter }}
Адреса (Обчислювальний сигнал): {{ fullAddressSignal() }}
Кількість викликів Getter: {{ getterCallCount }}
Кількість викликів Обчислювального сигналу: {{ signalCallCount }}
``
## **Вихід**
![pic](https://drive.javascript.org.ua/aa52b8ca451_02GWKUkFt2BT_KXXq4Vgsg_png)
## Порівняння продуктивності
1.
**Використання Getter:** Геттер
fullAddressGetterвикликається кожного разу, коли Angular запускає перевірку змін (change detection), навіть якщо жодне з полів адреси не було змінено. Подивіться на кількість викликів геттера.
2. **Використання Обчислювального сигналу:**
fullAddressSignalбуде перераховуватися лише тоді, коли зміниться будь-який із залежних сигналів (buildingName,buildingNumber` тощо). Це дозволяє уникнути зайвих перерахунків і покращує продуктивність у великих додатках.

Висновок

Використання сигналів (Signals) та обчислювальних сигналів (Computed Signals) в Angular Forms надає більш ефективний спосіб керування похідним станом. Зменшуючи кількість непотрібних перерахунків і тісно пов'язуючи реактивність зі змінами конкретних значень, сигнали пропонують потужне оновлення порівняно з традиційними геттерами в Angular додатках.

Розпочніть інтеграцію сигналів у ваші Angular форми вже сьогодні для більш продуктивної та масштабованої архітектури!

Перекладено з: Optimizing Angular Forms with Signals for Better Performance

Leave a Reply

Your email address will not be published. Required fields are marked *