22 kwi 2025
3 min

Wzorzec portów i adapterów dla NGRX Signal Store

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ą.

Podziel się artykułem

Zapisz się na nasz newsletter

Dołącz do community Angular.love i bądź na bieżąco z trendami.