Yazılım Mimarileri ve Tasarım Desenleri Üzerine

Signal Store ve NGXS – State Management’ta Esnekliği Artırma

Merhaba,

Malumunuz, Angular mimarisinde signal yapılanmasının gelmesi ve kullanılmaya başlamasıyla özellikle state management’ta olmak üzere farklı davranış fırsatları ortaya çıkmış bulunmaktadır. Bunlardan en meşhuru NgRx ekibi tarafından signal tabanlı state management için geliştirilmiş olan @ngrx/signals kütüphanesidir. Bu kütüphane, ihtiyaç noktalarında oldukça esnek ve etkili çözümler getirmekte ve bundan dolayı community tarafından geniş çapta kabul görmektedir. Özellikle hem local hem de global state’leri yönetmek için ve store’u ölçeklendirmek yahut paylaşılabilir kılmak için yardımcı olan ekstradan araçlar sunmaktadır.

Bunların dışında bu kütüphane ile ihtiyaçlarımıza göre istediğimiz store’u uyarlayabilmemiz için bir takım adaptörler sunulmuştur. Misal olarak NGXS, kendi Signal Store implementasyonlarını oluşturmak yerine @ngxs/store ile NgRx Signal Store bağlantısını sağlayacak olan bir dizi yardımcı araç oluşturmayı tercih etmiştir. Temel olarak NGXS store’a dayalı uygulamalar, NgRx Signal Store’u minimum çabayla entegre edebilmektedirler.

İnceleme

Şimdi bir inceleme üzerinden olayı pratiksel olarak ele alalım. Bir Angular uygulamasına öncelikle npm install @ngxs/store @ngxs/logger-plugin @ngxs/devtools-plugin talimatı eşliğinde NGXS’i ekleyelim. Ardından npm i @ngrx/signals@18.0.0-rc.1 talimatı ile de NgRx’i kuralım.

Şimdi NGXS ile Signal Store arasında bir köprü oluşturabilmek için kodda küçük bir çalışma yapılması gerekmektedir. Bu çalışma için NGXS’in sunduğu aşağıdaki iki fonksiyondan istifade edebiliriz;

İlk olarak withSelectors adaptörünü oluşturalım. Bu adaptör, selector’ların computed edilmiş signal’larını oluşturacaktır. Bunun için de aşağıdaki gibi withComputed fonksiyonundan istifade edeceğiz.

import { SelectorMap, createSelectMap } from "@ngxs/store";
import { signalStoreFeature, withComputed } from "@ngrx/signals";

export function withSelectors<T extends SelectorMap>(selectorMap: T) {
    return signalStoreFeature(withComputed(() => createSelectMap(selectorMap)))
}

Bunu aşağıdaki gibi store olarak yapılandırabiliriz;

import { signalStore } from "@ngrx/signals";
import { withSelectors } from "../shared/ngxs.utils";
import { CounterSelector } from "../selectors/counter.selector";

export const CounterStore = signalStore(
    withSelectors({
        counter: CounterSelector.getCount
    })
);

Benzer mantıkla action’ları da göndermemizi sağlayacak olan withActions adaptörünü oluşturalım.

export function withActions<T extends ActionMap>(actionMap: T) {
    return signalStoreFeature(withMethods(() => createDispatchMap(actionMap)))
}

Bu adaptör, createDispatchMap‘ten bir dispatcher haritası alan ve bunu Signal Store fonksiyonuna dönüştüren withMethods fonksiyonunu kullanmaktadır.

Bunu da biraz önce oluşturduğumuz store içerisinde aşağıdaki gibi yapılandırabiliriz;

export const CounterStore = signalStore(
    withSelectors({
        counter: CounterSelector.getCount
    }),
    withActions({
        increment: Increment,
        decrement: Decrement
    })
);

Ve bu store’u nihai olarak component’te aşağıdaki gibi kullanabiliriz;

import { Component, inject } from '@angular/core';
import { Decrement, Increment } from '../../actions/counter.actions';
import { CounterSelector } from '../../selectors/counter.selector';
import { CounterStore } from '../../stores/counter.store';
import { AComponent } from '../a/a.component';

@Component({
  selector: 'app-counter',
  standalone: true,
  imports: [AComponent],
  providers: [CounterStore],
  template: `
    <h3>NGXS Counter Example</h3>
    <!-- <h2>{{count$() }}</h2> -->
     <h2>{{counterStore.counter()}}</h2>
    <button (click)="increment()">Increment</button>
    <button (click)="decrement()">Decrement</button>

    <hr>
    <app-a></app-a>
  `
})
export class CounterComponent {
  readonly counterStore = inject(CounterStore);

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

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


  //#region NGXS State Management

  // count$ = this.store.selectSignal(CounterSelector.getCount);

  // constructor(private store: Store) {
  //   store.select(CounterSelector.getCount).subscribe(d => console.log(d));
  // }

  // increment() {
  //   this.store.dispatch(new Increment())
  //   this.counterStore
  // }

  // decrement() {
  //   this.store.dispatch(new Decrement());
  // }

  //#endregion
}

Evet, görüldüğü üzere özünde signalStoreFeature fonksiyonu aracılığıyla yaptığımız çalışmalar neticesinde NGXS’i genişleterek Signal Store ile uyumlu hale getirmiş bulunuyor ve entegrasyonu sağlamış oluyoruz.

Malum ki Angular’ın signal’ler üzerine olan ivmesi onlarca harici kütüphanede uyum çalışmasının yapılacağını göstermektedir. En azından bizler bu içeriğimizde NGXS ile bu uyumun manuel olarak nasıl sağlanabileceğini incelemiş oluyoruz.

Konuya daha iyi hakimiyet sağlayabilmek için içerikle uyumlu şekilde geliştirmiş olduğum aşağıdaki çalışmaya göz atmanızda fayda görmekteyim.
https://github.com/gncyyldz/signal-store-example-with-ngxs

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Mobil sürümden çık