Покращення форм в Angular за допомогою шаблонів ООП

Привіт 😀! Сезонні вітання та найкращі побажання на наступний рік!

Грудень 2024 був чудовим. Я хочу показати, як застосування принципів об'єктно-орієнтованого програмування (OOP) з Angular Reactive Forms може спростити розробку і сприяти слабкому зв'язку, абстракції, інкапсуляції та підтримуваності.

В Angular є два типи форм: Template-driven та Reactive Forms. Хоча деякі розробники віддають перевагу формам, керованим шаблонами, я особисто люблю Reactive Forms.

Слабкий зв'язок мінімізує зміни, необхідні при повторному використанні шаблону в вашому додатку або поза Angular. Ви не хочете вносити зміни в TypeScript код і при цьому оновлювати шаблон. Ці компоненти мають бути незалежними, подібно до принципу Separation of Concerns (SoC), а інкапсуляція гарантує, що деталі вашого коду приховані, а шаблон не має доступу до внутрішнього стану.

У цій статті ми розглянемо, як організувати Angular форму за допомогою OOP. Буде п'ять учасників: Actor A, Actor B, Actor C, Actor D і Actor E. Я буду Actor A.

Розпочнемо з практичної частини

1. Створення простої форми

Щоб створити базову форму в Angular, ви можете використовувати сервіс FormBuilder та FormGroup. Ось приклад того, як можна визначити форму в компоненті:

export class UpdateUserComponent {  

 protected formBuilder: FormBuilder = inject(FormBuilder);  
 protected form: FormGroup;  

 public ngOnInit(): void {  
 this.form = this.formBuilder.group({  
 firstName: [''],  
 lastName: [''],  
 country: [''],  
 gender: ['']  
 });  
 }  
}

У вашому шаблоні ви можете зв'язати форму та її елементи таким чином:



First Name        
Last Name        

2. Ініціалізація форми: найкращі практики

Actor B: Чому б не ініціалізувати форму безпосередньо, ось так?

 protected form: FormGroup = new FormGroup({  
    firstName: new FormControl(''),  
    lastName: new FormControl(''),  
    country: new FormControl('')  
   });  

Actor A: Це питання уподобань. Однак дозвольте пояснити, чому я віддаю перевагу підходу з ngOnInit.

 public ngOnInit(): void {  
    this.initForm();  
 }  

 protected initForm(): void {  
    this.form = this.formBuilder.group({  
    firstName: [''],  
    lastName: [''],  
    country: [''],  
    gender: ['']  
    });  
 }  

3. Принцип єдиної відповідальності (SRP)

Actor C: Чому саме цей підхід?

Actor A: Ми дотримуємось принципу **Single Responsibility Principle (SRP)**, який вимагає, щоб методи мали одну чітку мету або відповідальність. Розділяючи ініціалізацію форми на окремий метод, ми можемо зменшити технічний борг і зробити код більш підтримуваним. Коли ваша форма стане більш складною, наявність окремих методів для ініціалізації та конкретних задач буде корисним.

4. Інкапсуляція для видимості форми

Actor D: Чому форма оголошена як protected?

Actor A: Це приклад **інкапсуляції**. Внутрішні деталі компонента повинні бути приховані від зовнішнього світу. Замість того, щоб безпосередньо надавати доступ до форми, ви даєте доступ до неї через методи доступу або контракти, що дозволяє змінювати реалізацію форми без впливу на інші частини програми.

Ось приклад того, як можна експонувати форму світу:

 export class UpdateUserDetail {  
    protected form: FormGroup;  

    public get userForm(): FormGroup {  
    return this.form;  
    }  
 }  

У вашому шаблоні ви можете зв'язати форму, використовуючи userForm:



Зробимо форми більш гнучкими за допомогою інкапсуляції

**Actor E:** _Як я можу покращити спосіб зв'язування HTML полів форми з Angular формовими елементами? Зазвичай я робив це ось так_

First Name
Last Name
```

Actor A: Ви можете покращити це, використовуючи інкапсуляцію. Тепер елементи форми абстраговані в логіку компонента, що спрощує зміну внутрішніх деталей без необхідності оновлювати шаблон._ Якщо вам потрібно змінити будь-які внутрішні деталі, наприклад, перейменувати елемент форми або виконати додаткові перевірки, ви можете зробити це без зміни HTML шаблону. Цей підхід зменшує повторення коду та покращує підтримуваність. Ось як ви можете доступніше працювати з елементами форми:

 export class UpdateUserDetail {  
    protected form: FormGroup;  

    public get userForm(): FormGroup {  
    return this.form;  
    }  

    // Допоміжна функція для отримання елемента форми за назвою  
    private control(name: string): AbstractControl | null | undefined {  
    return this.form.get(name);  
    }  

    public get firstName(): AbstractControl | null | undefined {  
    return this.control('firstName');  
    }  

    public get lastName(): AbstractControl | null | undefined {  
    return this.control('lastName');  
    }  

    public get country(): AbstractControl | null | undefined {  
    return this.control('country');  
    }  
 }  

У вашому шаблоні прив'язуємо окремі елементи:


First Name            
Last Name            

6. Чому інкапсуляція покращує ваш код

Інкапсуляція дозволяє приховати внутрішні деталі. Наприклад, ви можете перейменовувати геттери або назви елементів форми, не змінюючи шаблон.

 public get countryOfResidence(): AbstractControl | null | undefined {  
    return this.form.get('country');  
 }  

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

Actor E: Зазвичай я перевіряю коректність форми ось так; як я можу покращити це за допомогою OOP?

 Update User  

Actor A: OOP дозволяє інкапсулювати перевірки коректності форми ось так:

 public get isFormValid(): boolean {  
    return this.form.valid;  
 }  
 Update User  

Це дозволяє вам змінювати логіку визначення коректності форми, не оновлюючи шаблон. Спосіб перевірки коректності форми або перевірки перед відправленням може змінюватися. Ці деталі мають бути абстраговані від HTML шаблону. Ви навіть можете виконувати додаткові перевірки перед тим, як вважати форму валідною.
Припустимо, у вас є

protected sendCode: Signal = signal(false);   

protected sendVerificationCode() {   
 this.sendCode.update(true);   
}

Ви додаєте перевірки до форми, щоб переконатися, що код був надісланий перед тим, як користувач зможе надіслати форму

protected get hasSentCode(): boolean {   
 return this.sendCode();   
}   

public get isFormValid(): boolean {   
 return this.hasSentCode && this.form.valid   
}

Actor D: Що станеться, якщо я зміню назву форми?

Actor A: Якщо ви зміните назву форми, вам зазвичай доведеться оновити HTML шаблон, щоб відобразити цю зміну. Однак, за допомогою інкапсуляції це значно спрощується.

Логіка отримання форми та її коректності абстрагується в компоненті, тому HTML шаблон не потребує оновлення, якщо це не є абсолютно необхідним. Наприклад, якщо ви зміните назву форми у вашому TypeScript коді, шаблон залишатиметься тим самим.

Завдяки інкапсуляції, єдине місце, де потрібно вносити зміни — це клас компонента, що зменшує ризик помилок у шаблоні при зміні форми. Цей підхід робить ваш код легшим для підтримки та менш схильним до помилок.

Застосовуючи ці принципи ООП в Angular формах, ви отримуєте кілька переваг:

Гнучкість: Ви можете змінювати структуру форми, логіку валідації або внутрішні перевірки (наприклад, користувацька валідація або управління станом) без зміни шаблону.

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

Чисті шаблони: Шаблони залишаються декларативними та простими, що забезпечує кращу читабельність та підтримуваність UI.

Масштабованість: Інкапсуляція допомагає управляти складністю форм, коли вони зростають, дозволяючи додавати нові правила валідації чи асинхронні операції без великих змін у шаблоні.

На цьому все на сьогодні. Сподіваюся, вам було корисно і цікаво. 🙂

Перекладено з: Enhancing Angular Forms with OOP Patterns

Leave a Reply

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