Управління станом в Angular, просто і зрозуміло: Частина 2

У попередній частині цієї серії блогів ми розглянули базовий підхід до управління станом в Angular, використовуючи сервіси та розуміння шаблону проектування Singleton. Ми також ознайомилися з концепцією реактивного стану за допомогою BehaviorSubject та Observable, щоб керувати даними нашого додатка. Тепер ми розширимо свої знання, заглибившись у ще дві концепції: похідний стан та складові потоки з використанням RxJS.

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

Що таке похідний стан?

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

Приклад похідного стану з використанням RxJS

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

Ми можемо досягти цього за допомогою операторів RxJS, таких як map, combineLatest та інших.

Ось як можна розширити CounterService, щоб експонувати похідні стани:

import { Injectable } from '@angular/core';  
import { BehaviorSubject } from 'rxjs';  
import { filter, map } from 'rxjs/operators';  

@Injectable({  
 providedIn: 'root',  
})  
export class CounterService {  
 private counterSubject = new BehaviorSubject(0); // Початкове значення лічильника - 1  

 // Observable для надання стану лічильника  
 counter$ = this.counterSubject.asObservable();  

 // Похідний стан: лічильник подвоєний  
 counterDoubled$ = this.counter$.pipe(map((counter) => counter * 2));  

 // Похідний стан: останнє парне число  
 latestEven$ = this.counter$.pipe(filter((counter) => counter % 2 === 0));  

 increment() {  
 this.counterSubject.next(this.counterSubject.value + 1);  
 }  

 decrement() {  
 this.counterSubject.next(this.counterSubject.value - 1);  
 }  

 reset() {  
 this.counterSubject.next(0);  
 }  
}

У цьому коді:

  • counterDoubled$ — це похідний стан, який виводить подвоєне поточне значення лічильника.
  • latestEven$ — це ще один похідний стан, який виводить останнє парне число.

Тепер, у компоненті, ви можете підписатися на ці Observables і відобразити похідні стани безпосередньо в шаблоні:

import { Component } from '@angular/core';  
import { Observable } from 'rxjs';  
import { CounterService } from '../../counter.service';  

@Component({  
 selector: 'my-app',  
 templateUrl: './app.component.html',  
 styleUrls: ['./app.component.css'],  
})  
export class AppComponent {  
 counter$: Observable;  
 counterDoubled$: Observable;  
 latestEven$: Observable;  

 constructor(private counterService: CounterService) {  
 this.counter$ = counterService.counter$;  
 this.counterDoubled$ = counterService.counterDoubled$;  
 this.latestEven$ = counterService.latestEven$;  
 }  

 increment() {  
 this.counterService.increment();  
 }  

 decrement() {  
 this.counterService.decrement();  
 }  

 reset() {  
 this.counterService.reset();  
 }  
}

Шаблон виглядатиме так:

Counter Value: {{ counter$ | async }}
Counter Doubled: {{ counterDoubled$ | async }}
Last Even: {{ latestEven$ | async }}
Increment    Decrement    Reset   

У цьому налаштуванні:

  • counterDoubled$ та lastEven$ — це похідні стани (derived state), які автоматично оновлюються, коли змінюється оригінальний counter$ Observable.
  • Тепер компонент стає більш модульним і менш залежним від складного управління станом.
    Кожен похідний стан обчислюється в момент потреби і може бути повторно використаний у різних компонентах.
  • Нижче ми покажемо, як покращити цей процес, щоб у шаблоні була лише одна підписка, оскільки кожен async pipe налаштовує свою власну підписку.

Складові потоки з RxJS

Ще одна потужна функція RxJS — це можливість об’єднувати кілька потоків стану та операцій разом для створення складних потоків даних у підтримуваний та реактивний спосіб.

Складові потоки дозволяють поєднувати або зливати observables з різних частин вашого додатка та застосовувати перетворення декларативним способом. Використовуючи оператори, такі як combineLatest, withLatestFrom, concat і merge, ви можете легко створювати складну логіку управління станом, яка реагує на кілька подій.

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

Приклад: Об'єднання кількох потоків

import { Component } from '@angular/core';  
import { combineLatest, Observable } from 'rxjs';  
import { CounterService } from '../../counter.service';  
import { map } from 'rxjs/operators';  

interface ViewModel {  
 counter: number;  
 counterDoubled: number;  
 latestEven: number;  
}  

@Component({  
 selector: 'my-app',  
 templateUrl: './app.component.html',  
 styleUrls: ['./app.component.css'],  
})  
export class AppComponent {  
 vm$: Observable;  

 constructor(private counterService: CounterService) {  
 // складений потік даних  
 this.vm$ = combineLatest([  
 this.counterService.counter$,  
 this.counterService.counterDoubled$,  
 this.counterService.latestEven$,  
 ]).pipe(  
 map(([counter, counterDoubled, latestEven]) => ({  
 counter,  
 counterDoubled,  
 latestEven,  
 }))  
 );  
 }  

 increment() {  
 this.counterService.increment();  
 }  

 decrement() {  
 this.counterService.decrement();  
 }  

 reset() {  
 this.counterService.reset();  
 }  
}

Тепер шаблон виглядатиме так:

Counter Value: {{ vm.counter }}
Counter Doubled: {{ vm.counterDoubled }}
Last Even: {{ vm.latestEven }}
Increment    Decrement    Reset   

Як це працює:

  1. Композиція стану: Ми об'єднали кілька потоків стану (counter$, counterDoubled$, та lastEven$) в один потік (vm$). Таким чином, шаблон потребує підписки лише на vm$ і автоматично отримує весь необхідний стан.
  2. Оновлення шаблону: Коли будь-який з observables змінюється, це ініціює перерахунок потоку vm$, і pipe async гарантує, що шаблон буде автоматично оновлений.

Переваги цього підходу:

  1. Єдине джерело правди: Шаблон підписується лише на vm$ — один observable, що агрегує всі релевантні стани, що полегшує управління і знижує ймовірність помилок.
  2. Ефективність: Вид буде перерендерено лише тоді, коли один з об’єднаних observables зміниться (лічильник, подвоєне значення або останнє парне).
  3. Чистіший код компонента: Компонент більше не потребує підписки на кілька observables або управління окремими частинами стану. Він просто споживає складений стан з різних observables.

У наступній частині цієї серії ми зробимо ще один крок уперед, додавши користувацькі дії до цих observable потоків даних для ініціювання оновлень стану в стилі redux.

Перекладено з: Angular State Managment Made Simple: Part 2

Leave a Reply

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