Czym jest NGRX Signal Store?
NgRx Signal Store to rozwiązanie do zarządzania stanem w aplikacjach Angular, zbudowane na podstawie sygnałów reaktywnych Angulara, wprowadzonych w Angular 16. Signal Store został zaprojektowany jako bardziej efektywna i prostsza alternatywa dla tradycyjnego NgRx Store. Łączy sygnały Angulara z ekosystemem NgRx, umożliwiając deweloperom zarządzanie stanem aplikacji w sposób bardziej reaktywny i łatwiejszy do zrozumienia w porównaniu do tradycyjnego NgRx Store, który w dużej mierze opiera się na obserwowalnych i akcjach.
Więcej o sygnałach na angular.love:
Więcej o Signal Store na angular.love:
Czym jest architektura heksagonalna (wzorzec portów i adapterów)?
Architektura heksagonalna (porty i adaptery) izoluje główną logikę biznesową od systemów zewnętrznych, wykorzystując interfejsy (porty) do definiowania interakcji oraz adaptery do realizacji implementacji. Taki podział zwiększa modularność, testowalność i elastyczność, pozwalając logice rdzenia pozostać niezależną od zmian infrastrukturalnych, co ułatwia utrzymanie i adaptację aplikacji.
Wzorzec heksagonalny w frameworku Angular, w połączeniu z potężnym mechanizmem Dependency Injection (DI), oferuje elegancki sposób zarządzania i upraszczania skomplikowanych interakcji w aplikacji. Pozwala deweloperom na kapsułkowanie złożonej logiki i udostępnianie uproszczonego API reszcie aplikacji, zwiększając modularność, łatwość utrzymania i testowalność.
Więcej o Dependency Injection na angular.love:
Więcej o architekturze heksagonalnej na angular.love:
- https://angular.love/pl/porty-i-adaptery-a-architektura-heksagonalna-czy-to-ten-sam-wzorzec
Moc mechanizmu inferencji typów w Signal Store
NgRx SignalStore to rozwiązanie do zarządzania stanem, które wykorzystuje mechanizm inferencji typów TypeScript do zapewnienia bardziej efektywnego i bezpiecznego typowania. Dzięki temu nie trzeba jawnie definiować typów. Ta zależność od inferencji typów zmniejsza ilość zbędnego kodu, zwiększa czytelność kodu i zapewnia bezpieczeństwo typów w całej aplikacji, co ułatwia utrzymanie i refaktoryzację oraz zapobiega błędom w czasie wykonywania wynikającym z niedopasowania typów. Rezultatem jest bardziej intuicyjne API, wspierające nowoczesne praktyki rozwoju Angular z minimalnym wysiłkiem i maksymalną niezawodnością.
Lekka implementacja wzorca Portów i Adapterów dla NGRX Signal Store
Połączenie NgRx Signal Store z architekturą heksagonalną (wzorzec portów i adapterów) oferuje potężne podejście do zarządzania stanem w aplikacjach Angular, jednocześnie zachowując korzyści z inferencji typów dostarczanych przez TypeScript. Dzięki tej integracji możemy wykorzystać elastyczność i modularność wzorca heksagonalnego, tworząc wyraźne granice między logiką biznesową a infrastrukturą aplikacji, bez wprowadzania zbędnej złożoności czy dodatkowego kodu.
Stosując to podejście, zyskujemy korzyści architektury heksagonalnej, takie jak lepsza testowalność i łatwiejsze utrzymanie kodu, jednocześnie utrzymując prostotę i minimalizm oferowany przez Signal Store.
Zazwyczaj w TypeScript implementacja adaptera opiera się na mechanizmie implementacji interfejsów, ale tym razem użyjemy operatora satisfies:
import { Type } from "@angular/core";
export interface Fruit {
id: string;
name: string;
}
export interface FruitService {
fruits: Signal<Fruit[]>;
loadFruits(): void;
}
const FruitServiceAdapter = signalStore(
withState({ fruits: [] as Fruit[] }),
withMethods((store) => {
return {
loadFruits: async () => {
const fruits = await fetch('https://api.example.com/fruits').then(
(res) => res.json()
);
patchState(store, {
fruits,
});
},
};
})
) satisfies Type<FruitService>;
Operator satisfies wymusza zgodność wartości z określonym typem, ale nie zmienia typu wartości (np. wynikającego z mechanizmu inferencji typów). W ten sposób nie nadpisujemy typu wywnioskowanego dla Signal Store, ale zapewniamy poprawną implementację portu.
Teraz możemy przygotować wygodne narzędzia do wstrzykiwania adaptera pod port:
const fruitServiceInjectionToken = new InjectionToken<FruitsService>(
'fruits-service'
);
export function provideFruitService(): Provider {
return {
provide: fruitServiceInjectionToken,
useClass: FruitServiceAdapter,
};
}
export function injectFruitService(): FruitsService {
return inject(fruitServiceInjectionToken);
}
A tak wygląda ich użycie wewnątrz komponentu lub innej usługi:
@Component({
selector: 'app-root',
standalone: true,
template: ``,
providers: [provideFruitService()],
})
export class App {
private fruitService = injectFruitService();
constructor() {
this.fruitService.loadFruits();
}
}
Stackblitz:
https://stackblitz.com/edit/stackblitz-starters-pbmwtt?file=src%2Ffruit.service.ts
Możemy także uprościć narzędzia do wstrzykiwania, korzystając z klas abstrakcyjnych. Podejście to zostało opisane w artykule angular.love o Portach i Adapterach:
- https://angular.love/pl/porty-i-adaptery-a-architektura-heksagonalna-czy-to-ten-sam-wzorzec
Podsumowanie
Połączenie NgRx SignalStore z architekturą heksagonalną oferuje nowoczesne, bezpieczne typowo podejście do zarządzania stanem w aplikacjach Angular. Dzięki wykorzystaniu potężnego mechanizmu inferencji typów TypeScript możemy zachować prostotę i minimalizm Signal Store, jednocześnie korzystając z modularności i testowalności wzorca heksagonalnego.
To podejście nie tylko redukuje ilość zbędnego kodu i poprawia czytelność, ale również zapewnia solidną architekturę aplikacji z wyraźnym podziałem odpowiedzialności. Przy minimalnym dodatkowym kodzie można zaimplementować przejrzyste, łatwe w utrzymaniu i skalowalne rozwiązanie do zarządzania stanem, które łączy paradygmat reaktywny z zasadami czystej architektury. Ta synergia ułatwia deweloperom budowanie i utrzymanie złożonych aplikacji z pewnością i efektywnością.