Takip et

Angular Uygulamalarında Durum Yönetimi: NGXS ile Güçlü ve Ölçeklenebilir Çözümler

Angular Uygulamalarında Durum Yönetimi: NGXS ile Güçlü ve Ölçeklenebilir Çözümler Giriş Modern web uygulamaları, özellikle Angular gibi

Angular Uygulamalarında Durum Yönetimi: NGXS ile Güçlü ve Ölçeklenebilir Çözümler

Giriş

Modern web uygulamaları, özellikle Angular gibi tek sayfa uygulama (SPA) çatılarında geliştirilenler, genellikle karmaşık ve dinamik veri akışlarına sahiptir. Kullanıcı etkileşimleri, sunucu istekleri, bileşenler arası iletişim ve uygulamanın genel durumu (state) bu karmaşıklığın temelini oluşturur. Uygulama büyüdükçe, bu durumun yönetimi giderek zorlaşır ve kontrol dışı bir hal alabilir. Durumun nerede depolandığı, nasıl güncellendiği ve farklı bileşenlerin bu duruma nasıl eriştiği konuları netleşmediğinde, hatalar ayıklanması güçleşir, uygulamanın bakımı zorlaşır ve ölçeklenebilirlik ciddi şekilde etkilenir.

İşte tam bu noktada durum yönetimi (state management) kütüphaneleri devreye girer. Durum yönetimi, uygulamanın tüm verilerini merkezi bir yerde tutarak, bu verilere erişim ve güncelleme süreçlerini tutarlı ve öngörülebilir bir şekilde standartlaştırmayı amaçlar. Angular ekosisteminde NgRx, Akita gibi popüler çözümler bulunsa da, son yıllarda NGXS, basitliği, güçlü TypeScript entegrasyonu ve daha az “boilerplate” (tekrarlayan kod) gerektirmesiyle dikkat çekmektedir. Bu makalede, Angular uygulamalarında NGXS kullanarak durum yönetiminin nasıl etkili bir şekilde yapılacağını, temel kavramlarından gelişmiş özelliklerine kadar detaylı bir şekilde inceleyeceğiz.

Temel Durum Yönetimi Kavramları

Durum yönetimi çözümlerinin çoğu, Redux mimarisinden ilham alır ve belirli temel kavramlar üzerine kuruludur. NGXS de bu kavramları kendi TypeScript ve RxJS odaklı yapısıyla yorumlar:

* Durum (State): Uygulamanın belirli bir andaki tüm verilerini temsil eden tek, değiştirilemez (immutable) bir nesnedir. Örneğin, bir alışveriş sepetindeki ürünler, kullanıcının oturum bilgileri veya bir formun mevcut değeri birer durum parçasıdır.
* Eylem (Action): Durumda bir değişiklik yapma niyetini tanımlayan basit bir mesajdır. Eylemler, kullanıcının bir butona tıklaması, sunucudan veri gelmesi veya bir zamanlayıcının sona ermesi gibi olaylar sonucunda tetiklenir. Eylemler genellikle bir type özelliğine ve duruma etki edecek verileri taşıyan bir payload‘a sahiptir.
* Depo (Store): Uygulamanın tüm durumunu barındıran merkezi bir nesnedir. Bileşenler, duruma doğrudan erişmek yerine, depo aracılığıyla eylemler gönderir ve durumu seçiciler (selectors) aracılığıyla okur.
* Seçici (Selector): Depodaki durumdan belirli veri parçalarını ayıklamak ve dönüştürmek için kullanılan saf (pure) fonksiyonlardır. Seçiciler, bileşenlerin yalnızca ihtiyaç duydukları verilere erişmesini sağlar ve durumun yapısındaki değişikliklerden etkilenmemelerine yardımcı olur.
* İşleyici (Handler / Reducer): Bir eyleme yanıt olarak mevcut durumu alan ve eylemin yüküne (payload) göre yeni bir durum döndüren fonksiyonlardır. NGXS’te bu işlev @Action dekoratörü ile işaretlenmiş metotlar tarafından yerine getirilir. İşleyiciler, daima mevcut durumu değiştirmek yerine, durumun yeni bir kopyasını döndürerek değiştirilemezlik (immutability) prensibini korumalıdır.
* Yan Etki (Side Effect): Durumda doğrudan değişiklik yapmayan, ancak eylemler sonucunda tetiklenen asenkron işlemlerdir. API çağrıları, veritabanı işlemleri, navigasyon veya zamanlayıcılar gibi işlemler yan etkilere örnek olarak verilebilir. NGXS’te yan etkiler genellikle @Action işleyicileri içinde RxJS operatörleri kullanılarak yönetilir.

NGXS Nedir? Neden NGXS?

NGXS (Next Generation eXtendable State management) , Angular uygulamaları için RxJS ve TypeScript üzerine inşa edilmiş, Redux ilhamlı bir durum yönetim kütüphanesidir. Geleneksel Redux yapılarının aksine, NGXS, dekoratörler (decorators) ve sınıflar (classes) kullanarak daha az “boilerplate” kodu ile daha okunabilir ve yönetilebilir bir kod tabanı sunmayı hedefler.

NGXS’i tercih etmek için birkaç önemli neden bulunmaktadır:

* Basit API ve Az Boilerplate: NGXS, Redux’a kıyasla daha az tekrarlayan kod gerektirir. Dekoratif yapısı sayesinde eylemler, durum işleyicileri ve seçiciler tek bir State sınıfı içinde tanımlanabilir, bu da kodu daha derli toplu hale getirir.
* TypeScript Odaklı Tasarım: NGXS, TypeScript’in tüm avantajlarından yararlanır. Tür güvenliği, otomatik tamamlama ve derleme zamanı hatalarının tespiti, büyük projelerde geliştirme sürecini hızlandırır ve hataları azaltır.
* RxJS Entegrasyonu: NGXS, Angular’ın reaktif doğasına mükemmel uyum sağlayan RxJS ile derinlemesine entegredir. Asenkron işlemlerin ve yan etkilerin yönetimi, RxJS operatörleri ile kolayca yapılabilir.
* Performans: Değiştirilemez durum (immutable state) prensibini benimsemesi ve RxJS’in güçlü akış yönetimi sayesinde NGXS, performanslı bir durum yönetimi sunar.
* Modüler Yapı: Uygulamanın durumunu mantıksal modüllere ayırarak yönetme imkanı sunar. Bu, büyük ve karmaşık uygulamalarda kodun daha düzenli olmasını sağlar.
* Geliştirici Araçları Desteği: Redux DevTools gibi popüler geliştirici araçlarıyla entegrasyon sayesinde, durum değişikliklerini izlemek, eylemleri kaydetmek ve hata ayıklamak çok daha kolaydır.
* Aktif Topluluk ve Dokümantasyon: NGXS, aktif bir geliştirici topluluğuna ve kapsamlı dokümantasyona sahiptir, bu da öğrenme sürecini ve sorun gidermeyi kolaylaştırır.

NGXS Kurulumu ve Temel Kullanım

NGXS’i Angular projenize dahil etmek oldukça basittir. İlk adım olarak @ngxs/store paketini kurmalısınız:

npm install @ngxs/store

Ardından, app.module.ts dosyanızda NgxsModule‘ü import edip uygulamanızın kök modülüne kaydetmeniz gerekir:

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { NgxsModule } from '@ngxs/store';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    NgxsModule.forRoot([]) // Burada state'lerinizi tanımlayacaksınız
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Geliştirme sürecini kolaylaştırmak için NGXS’in geliştirici araçları eklentilerini de kurmanız önerilir:

npm install @ngxs/devtools-plugin @ngxs/logger-plugin

Bu eklentileri de NgxsModule.forRoot metoduna ekleyerek etkinleştirebilirsiniz:

// app.module.ts
import { NgxsModule } from '@ngxs/store';
import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';
import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin';

@NgModule({
  imports: [
    // ...
    NgxsModule.forRoot([], { developmentMode: !environment.production }),
    NgxsReduxDevtoolsPluginModule.forRoot({
      disabled: environment.production
    }),
    NgxsLoggerPluginModule.forRoot({
      disabled: environment.production
    })
  ],
  // ...
})
export class AppModule { }

developmentMode ayarı ve eklentilerin disabled özelliği, üretim ortamında bu araçların devre dışı bırakılmasını sağlar, bu da performans ve güvenlik açısından önemlidir.

Bir NGXS State Tanımlamak

NGXS’te durum (state), bir TypeScript sınıfı olarak tanımlanır ve @State dekoratörü ile işaretlenir. Bu sınıf, durumun varsayılan değerlerini (defaults) ve eylem işleyicilerini (action handlers) içerir.

Örnek olarak, bir Todo uygulamasının durumunu yönetmek için bir TodoState tanımlayalım:

// models/todo.model.ts
export interface Todo {
  id: string;
  title: string;
  completed: boolean;
}

// state/todo.state.ts
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { Todo } from '../models/todo.model';

// State'in yapısını tanımlayan arayüz
export interface TodoStateModel {
  todos: Todo[];
}

@State({
  name: 'todo', // State'in benzersiz adı
  defaults: {
    todos: [] // State'in başlangıç/varsayılan değeri
  }
})
export class TodoState {
  // Eylem işleyicileri ve seçiciler buraya gelecek
}

@State dekoratörü iki ana özellik alır:
* name: Durumun global olarak erişileceği benzersiz adıdır.
* defaults: Durumun başlangıçta alacağı varsayılan değerlerdir.

Eylemler (Actions) Oluşturma

Eylemler, duruma ne tür bir değişiklik yapılacağını bildiren mesajlardır. NGXS’te eylemler, static readonly type özelliğine sahip basit TypeScript sınıfları olarak tanımlanır. Bu type özelliği, eylemin benzersiz tanımlayıcısıdır. Eylemin taşıması gereken veriler (payload), constructor üzerinden iletilir.

// state/todo.actions.ts
import { Todo } from '../models/todo.model';

export class AddTodo {
  static readonly type = '[Todo] Add Todo'; // Eylemin benzersiz tipi
  constructor(public payload: Todo) {} // Eylemin taşıdığı veri
}

export class RemoveTodo {
  static readonly type = '[Todo] Remove Todo';
  constructor(public id: string) {}
}

export class ToggleTodo {
  static readonly type = '[Todo] Toggle Todo';
  constructor(public id: string) {}
}

export class LoadTodos {
  static readonly type = '[Todo] Load Todos';
}

export class SetTodos {
  static readonly type = '[Todo] Set Todos';
  constructor(public payload: Todo[]) {}
}

Bu eylem sınıfları, uygulamanızda Store servisi aracılığıyla gönderilecektir.

Eylemleri İşleme (Action Handling)

Eylemler, State sınıfı içindeki metotlar tarafından @Action dekoratörü kullanılarak işlenir. Bu metotlar, StateContext (kısaca ctx) nesnesini ve eylemin kendisini parametre olarak alır. StateContext, mevcut duruma erişim (getState()), durumu güncelleme (setState(), patchState()) ve diğer eylemleri gönderme (dispatch()) gibi yöntemler sağlar.

Durum güncellenirken, değiştirilemezlik (immutability) prensibine kesinlikle uyulmalıdır. Yani, mevcut durumu doğrudan değiştirmek yerine, durumun yeni bir kopyası oluşturulmalı ve bu kopya üzerinde değişiklikler yapılmalıdır. NGXS’in setState ve patchState metotları bu süreci kolaylaştırır.

setState(): Durumun tamamını yeni bir nesne ile değiştirir.
patchState(): Durumun yalnızca belirli bir bölümünü günceller (derin bir birleştirme yapar).

TodoState sınıfımıza eylem işleyicilerini ekleyelim:

// state/todo.state.ts
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { Todo } from '../models/todo.model';
import { AddTodo, RemoveTodo, ToggleTodo, SetTodos } from './todo.actions';
import { v4 as uuidv4 } from 'uuid'; // Benzersiz ID'ler için

export interface TodoStateModel {
  todos: Todo[];
}

@State({
  name: 'todo',
  defaults: {
    todos: []
  }
})
export class TodoState {

  @Action(AddTodo)
  addTodo(ctx: StateContext, action: AddTodo) {
    const state = ctx.getState();
    const newTodo = { ...action.payload, id: uuidv4(), completed: false };
    ctx.setState({
      ...state,
      todos: [...state.todos, newTodo]
    });
  }

  @Action(RemoveTodo)
  removeTodo(ctx: StateContext, action: RemoveTodo) {
    const state = ctx.getState();
    ctx.patchState({
      todos: state.todos.filter(todo => todo.id !== action.id)
    });
  }

  @Action(ToggleTodo)
  toggleTodo(ctx: StateContext, action: ToggleTodo) {
    const state = ctx.getState();
    ctx.patchState({
      todos: state.todos.map(todo =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      )
    });
  }

  @Action(SetTodos)
  setTodos(ctx: StateContext, action: SetTodos) {
    ctx.patchState({
      todos: action.payload
    });
  }
}

Yukarıdaki örnekte, addTodo metodunda setState kullanarak tüm durumu güncellerken, removeTodo ve toggleTodo metotlarında patchState kullanarak sadece todos dizisini güncelledik. patchState genellikle daha kullanışlıdır çünkü durumun diğer kısımlarını manuel olarak kopyalamak zorunda kalmazsınız.

Bu TodoState sınıfını app.module.ts‘deki NgxsModule.forRoot metoduna eklemeyi unutmayın:

// app.module.ts
import { NgxsModule } from '@ngxs/store';
import { TodoState } from './state/todo.state'; // State sınıfını import edin

@NgModule({
  imports: [
    // ...
    NgxsModule.forRoot([TodoState], { developmentMode: !environment.production }), // State'i buraya ekleyin
    // ...
  ],
  // ...
})
export class AppModule { }

Verileri Seçicilerle (Selectors) Okuma

Seçiciler, durumdan veri okumak için kullanılan saf fonksiyonlardır. Bileşenlerin doğrudan duruma erişmesini engeller ve durumun yapısındaki değişikliklerin bileşenleri etkilemesini önler. NGXS’te seçiciler, @Selector dekoratörü ile işaretlenmiş static metotlar olarak tanımlanır.

// state/todo.state.ts
import { State, Action, StateContext, Selector } from '@ngxs/store';
// ... diğer importlar

@State({
  name: 'todo',
  defaults: {
    todos: []
  }
})
export class TodoState {
  // ... eylem işleyicileri

  @Selector()
  static getAllTodos(state: TodoStateModel) {
    return state.todos;
  }

  @Selector()
  static getCompletedTodos(state: TodoStateModel) {
    return state.todos.filter(todo => todo.completed);
  }

  @Selector()
  static getActiveTodos(state: TodoStateModel) {
    return state.todos.filter(todo => !todo.completed);
  }

  // Parametreli seçici örneği
  static getTodoById(id: string) {
    return createSelector([TodoState], (state: TodoStateModel) => {
      return state.todos.find(todo => todo.id === id);
    });
  }
}

Bileşenlerde bu seçicileri kullanmak için Store servisini enjekte etmeli ve select() veya selectSnapshot() metotlarını kullanmalısınız:

// components/todo-list/todo-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { Observable } from 'rxjs';
import { Todo } from '../../models/todo.model';
import { AddTodo, RemoveTodo, ToggleTodo } from '../../state/todo.actions';
import { TodoState } from '../../state/todo.state';

@Component({
  selector: 'app-todo-list',
  templateUrl: './todo-list.component.html',
  styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {

  @Select(TodoState.getAllTodos) todos$!: Observable;
  @Select(TodoState.getCompletedTodos) completedTodos$!: Observable;

  constructor(private store: Store) { }

  ngOnInit(): void {
    // Durumdan anlık veri almak için selectSnapshot()
    const allTodosSnapshot = this.store.selectSnapshot(TodoState.getAllTodos);
    console.log('Anlık tüm todolar:', allTodosSnapshot);
  }

  addTodo(title: string) {
    const newTodo: Todo = { id: '', title, completed: false }; // ID state içinde atanacak
    this.store.dispatch(new AddTodo(newTodo));
  }

  removeTodo(id: string) {
    this.store.dispatch(new RemoveTodo(id));
  }

  toggleTodo(id: string) {
    this.store.dispatch(new ToggleTodo(id));
  }

  getTodoDetails(id: string) {
    // Parametreli seçiciyi kullanma
    this.store.select(TodoState.getTodoById(id)).subscribe(todo => {
      console.log(Todo ${id} detayları:, todo);
    });
  }
}

@Select dekoratörü, bir seçiciyi doğrudan bir Observable‘a bağlayarak bileşenlerde veri akışını kolaylaştırır. selectSnapshot() ise o anki durumun tek seferlik bir kopyasını almanızı sağlar.

Asenkron İşlemler ve Yan Etkiler

Gerçek dünya uygulamalarında, durum değişiklikleri genellikle asenkron işlemler (API çağrıları, zamanlayıcılar vb.) içerir. NGXS’te bu yan etkiler, @Action işleyicileri içinde RxJS operatörleri kullanılarak yönetilir. Genellikle bir eylem tetiklenir, bu eylem bir API çağrısını başlatır ve API’den gelen yanıta göre başka bir eylem (örneğin, başarılı veya başarısız bir eylem) gönderilerek durum güncellenir.

Örnek olarak, bir API’den todo listesi yükleyelim:

// services/todo.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { Todo } from '../models/todo.model';

@Injectable({ providedIn: 'root' })
export class TodoService {
  private apiUrl = 'https://jsonplaceholder.typicode.com/todos'; // Örnek API

  constructor(private http: HttpClient) {}

  getTodos(): Observable {
    // Gerçek bir API çağrısı
    return this.http.get(this.apiUrl);
    // Veya simüle edilmiş bir çağrı
    // return of([
    //   { id: '1', title: 'Learn NGXS', completed: false },
    //   { id: '2', title: 'Build an app', completed: true }
    // ]);
  }
}

// state/todo.state.ts
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { Todo } from '../models/todo.model';
import { AddTodo, RemoveTodo, ToggleTodo, LoadTodos, SetTodos } from './todo.actions';
import { TodoService } from '../services/todo.service';
import { tap, catchError } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { throwError } from 'rxjs';

export interface TodoStateModel {
  todos: Todo[];
  loading: boolean; // Yükleme durumu
  error: string | null; // Hata mesajı
}

@Injectable() // Servis enjeksiyonu için gerekli
@State({
  name: 'todo',
  defaults: {
    todos: [],
    loading: false,
    error: null
  }
})
export class TodoState {
  constructor(private todoService: TodoService) {} // Servisi enjekte et

  // ... diğer eylem işleyicileri

  @Action(LoadTodos)
  loadTodos(ctx: StateContext) {
    ctx.patchState({ loading: true, error: null }); // Yükleme başladı

    return this.todoService.getTodos().pipe(
      tap(todos => {
        // API'den gelen verilerle durumu güncellemek için başka bir eylem gönder
        ctx.dispatch(new SetTodos(todos));
        ctx.patchState({ loading: false }); // Yükleme bitti
      }),
      catchError(error => {
        ctx.patchState({
          loading: false,
          error: 'Failed to load todos.' // Hata mesajını kaydet
        });
        return throwError(() => new Error('Failed to load todos')); // Hata akışını devam ettir
      })
    );
  }

  @Selector()
  static isLoading(state: TodoStateModel) {
    return state.loading;
  }

  @Selector()
  static getError(state: TodoStateModel) {
    return state.error;
  }
}

loadTodos eyleminde, TodoService‘den veri çekilir. tap operatörü, API yanıtını aldıktan sonra SetTodos eylemini gönderir ve yükleme durumunu günceller. catchError operatörü ise herhangi bir hata durumunda durumu güncelleyerek kullanıcıya bilgi verilmesini sağlar. TodoState sınıfına @Injectable() dekoratörünü eklemeyi ve AppModule‘deki providers dizisine TodoService‘i eklemeyi unutmayın.

NGXS Pluginleri

NGXS, çekirdek işlevselliğinin yanı sıra, geliştirme ve üretim ortamlarında faydalı olabilecek bir dizi eklenti (plugin) sunar:

* @ngxs/devtools-plugin: Redux DevTools uzantısı ile entegrasyon sağlar. Bu, durum değişikliklerini, eylem geçmişini ve zaman yolculuğu hata ayıklamasını mümkün kılar.
* @ngxs/logger-plugin: Konsola her eylemi ve durum değişikliğini loglar. Geliştirme sırasında durumu ve eylem akışını anlamak için çok kullanışlıdır.
* @ngxs/storage-plugin: Durumu localStorage veya sessionStorage gibi tarayıcı depolama alanlarında kalıcı hale getirir. Bu sayede sayfa yenilendiğinde bile durum korunabilir.
* @ngxs/form-plugin: Angular formları ile NGXS durumunu senkronize etmeyi kolaylaştırır.
* @ngxs/websocket-plugin: WebSocket olaylarını NGXS eylemlerine bağlamak için kullanılır.

Bu eklentilerin kurulumu ve kullanımı, NgxsModule.forRoot metoduna eklenmeleriyle gerçekleştirilir, tıpkı devtools ve logger örneklerinde olduğu gibi.

// app.module.ts
import { NgxsStoragePluginModule } from '@ngxs/storage-plugin';

@NgModule({
  imports: [
    // ...
    NgxsModule.forRoot([TodoState], { developmentMode: !environment.production }),
    NgxsReduxDevtoolsPluginModule.forRoot({ disabled: environment.production }),
    NgxsLoggerPluginModule.forRoot({ disabled: environment.production }),
    NgxsStoragePluginModule.forRoot({
      key: ['todo.todos'] // Hangi state'lerin depolanacağını belirtin
    })
  ],
  // ...
})
export class AppModule { }

Yukarıdaki örnekte key özelliği, todo state’i içindeki sadece todos bölümünün kalıcı hale getirileceğini belirtir.

Gelişmiş Konular

Modüler Yapı ve Feature State’ler

Büyük uygulamalarda, tüm state’leri tek bir NgxsModule.forRoot içinde tutmak yerine, bunları mantıksal modüllere ayırmak daha iyi bir yaklaşımdır. NGXS, NgxsModule.forFeature metodu ile bunu destekler. Bu, her Angular modülünün kendi ilgili state’lerini tanımlamasına olanak tanır.

// user.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgxsModule } from '@ngxs/store';
import { UserState } from './state/user.state'; // User state'ini import edin

@NgModule({
  imports: [
    CommonModule,
    NgxsModule.forFeature([UserState]) // Feature state'i burada kaydet
  ],
  declarations: []
})
export class UserModule { }

Bu sayede, sadece ilgili modül yüklendiğinde (lazy loading ile) o modülün state’leri de yüklenir, bu da başlangıç performansını artırır ve kodun daha düzenli olmasını sağlar.

State Birleştirme (State Composition)

NGXS, @Selector dekoratörünü kullanarak birden fazla state’ten veri birleştiren veya dönüştüren seçiciler oluşturmayı destekler. Bu, karmaşık veri görünümleri oluşturmak için güçlü bir mekanizmadır.

// state/app.state.ts (veya başka bir state)
import { State, Selector } from '@ngxs/store';
import { TodoState, TodoStateModel } from './todo.state';
import { UserState, UserStateModel } from './user.state';

@State({
  name: 'app',
  defaults: {}
})
export class AppState {

  @Selector([TodoState, UserState])
  static getDashboardData(todoState: TodoStateModel, userState: UserStateModel) {
    // Her iki state'ten verileri alıp birleştirme
    const totalTodos = todoState.todos.length;
    const completedTodos = todoState.todos.filter(todo => todo.completed).length;
    const userName = userState.user?.name || 'Guest';

    return {
      userName,
      totalTodos,
      completedTodos,
      pendingTodos: totalTodos - completedTodos
    };
  }
}

Bu seçici, TodoState ve UserState‘ten gelen verileri alarak bir dashboard için özet bir görünüm oluşturur.

Hata Yönetimi

Asenkron işlemlerde hata yönetimi kritik öneme sahiptir. NGXS’te, @Action işleyicileri içinde RxJS’in catchError operatörü kullanılarak hatalar yakalanabilir ve duruma kaydedilebilir. Bu, UI’da kullanıcıya hata mesajları göstermek veya hata durumuna göre farklı eylemler tetiklemek için kullanılabilir.

// ... TodoState içindeki loadTodos metodundan örnek
@Action(LoadTodos)
loadTodos(ctx: StateContext) {
  ctx.patchState({ loading: true, error: null });

  return this.todoService.getTodos().pipe(
    tap(todos => {
      ctx.dispatch(new SetTodos(todos));
      ctx.patchState({ loading: false });
    }),
    catchError(error => {
      console.error('API Error:', error);
      ctx.patchState({
        loading: false,
        error: 'Todo listesi yüklenirken bir hata oluştu.'
      });
      return throwError(() => new Error('API çağrısı başarısız oldu')); // Hata akışını devam ettir
    })
  );
}

Test Edilebilirlik

NGXS, durum yönetim kodunuzun kolayca test edilebilir olmasını sağlar. State sınıfları, servisler ve eylemler bağımsız birimler olarak test edilebilir. @ngxs/store/testing modülü, test ortamında NGXS store’unu mock’lamak ve test senaryolarını yazmak için yardımcı araçlar sunar.

// state/todo.state.spec.ts (Örnek Jest testi)
import { TestBed } from '@angular/core/testing';
import { NgxsModule, Store } from '@ngxs/store';
import { TodoState, TodoStateModel } from './todo.state';
import { AddTodo, RemoveTodo, ToggleTodo } from './todo.actions';
import { Todo } from '../models/todo.model';

describe('TodoState', () => {
  let store: Store;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [NgxsModule.forRoot([TodoState])]
    });
    store = TestBed.inject(Store);
  });

  it('should have a default state', () => {
    const todos = store.selectSnapshot(TodoState.getAllTodos);
    expect(todos).toEqual([]);
  });

  it('should add a todo', () => {
    const newTodo: Todo = { id: '', title: 'Test Todo', completed: false };
    store.dispatch(new AddTodo(newTodo));
    const todos = store.selectSnapshot(TodoState.getAllTodos);
    expect(todos.length).toBe(1);
    expect(todos[0].title).toBe('Test Todo');
  });

  it('should remove a todo', () => {
    const todo1: Todo = { id: '1', title: 'Test Todo 1', completed: false };
    const todo2: Todo = { id: '2', title: 'Test Todo 2', completed: false };
    store.dispatch(new AddTodo(todo1));
    store.dispatch(new AddTodo(todo2));

    store.dispatch(new RemoveTodo('1'));
    const todos = store.selectSnapshot(TodoState.getAllTodos);
    expect(todos.length).toBe(1);
    expect(todos[0].id).toBe('2');
  });

  // Diğer action ve selector testleri...
});

NGXS’in Avantajları ve Dezavantajları

Her araç gibi NGXS’in de kendine özgü avantajları ve dezavantajları vardır:

Avantajları:
* Az Boilerplate: Redux tabanlı diğer çözümlere göre daha az tekrarlayan kod gerektirir, bu da geliştirme hızını artırır.
* TypeScript Desteği: Güçlü TypeScript entegrasyonu sayesinde tür güvenliği ve daha iyi geliştirici deneyimi sunar.
* Dekoratör Tabanlı API: @State, @Action, @Selector gibi dekoratörler, kodu daha okunabilir ve yönetilebilir hale getirir.
* RxJS Entegrasyonu: Angular’ın reaktif doğasına uyumlu, asenkron işlemleri ve veri akışlarını kolayca yönetir.
* Modüler Yapı: forFeature özelliği ile büyük uygulamalarda state’leri modüllere ayırma imkanı sunar.
* Geliştirici Araçları: Redux DevTools ve logger eklentileri sayesinde hata ayıklama ve durum izleme kolaylaşır.
* Performans: Immutable state prensibi ve RxJS’in verimli kullanımı ile performanslı çalışır.

Dezavantajları:
* RxJS Bilgisi Gerekliliği: RxJS’e aşina olmayan geliştiriciler için öğrenme eğrisi olabilir.
* Yeni Öğrenme Eğrisi: Redux veya NgRx gibi daha köklü çözümlere alışkın geliştiriciler için farklı bir API’ye adapte olma süreci gerektirebilir.
* NgRx Kadar Olgun Değil: NgRx kadar uzun bir geçmişe ve geniş bir ekosisteme sahip değildir, ancak hızla gelişmektedir.
* Çok Fazla State Sınıfı: Çok büyük projelerde, her bir özellik için ayrı bir State sınıfı oluşturmak, dosya sayısını artırabilir, ancak bu durum modüler yaklaşımla yönetilebilir.

NGXS vs. NgRx (Kısa Karşılaştırma)

Angular ekosisteminde durum yönetimi denince akla gelen ilk çözümlerden biri NgRx’tir. NGXS ve NgRx benzer prensiplere dayanır ancak farklı uygulama yaklaşımları sunarlar:

* Boilerplate: NGXS, dekoratör tabanlı yapısı sayesinde NgRx’e göre daha az boilerplate kod gerektirir. NgRx, action, reducer, effect ve selector’ları ayrı ayrı tanımlamayı gerektirirken, NGXS bunları tek bir State sınıfı içinde birleştirir.
* Öğrenme Eğrisi: NGXS genellikle NgRx’e göre daha hızlı öğrenilebilir ve başlangıç için daha az karmaşıktır. NgRx’in daha katı Redux prensipleri ve daha fazla soyutlama katmanı vardır.
* Kod Yapısı: NGXS, TypeScript sınıfları ve dekoratörler kullanarak daha “Angular-vari” bir kodlama deneyimi sunar. NgRx, daha fonksiyonel bir yaklaşımı benimser.
* Kontrol ve Esneklik: NgRx, Redux’ın tüm gücünü ve esnekliğini sunar, bu da çok karmaşık senaryolarda daha fazla kontrol sağlayabilir. NGXS ise daha hızlı geliştirme ve daha az kod ile benzer sonuçlar elde etmeyi hedefler.
* Topluluk ve Olgunluk: NgRx, daha uzun bir geçmişe sahip olduğu için daha büyük bir topluluğa, daha fazla kaynağa ve daha olgun bir ekosisteme sahiptir. NGXS ise hızla büyüyen ve aktif bir topluluğa sahiptir.

Seçim, projenin büyüklüğü, takımın RxJS ve Redux deneyimi, tercih edilen kodlama stili ve geliştirme hızı gibi faktörlere bağlıdır. Daha hızlı bir başlangıç ve daha az kod arayanlar için NGXS harika bir seçenek olabilirken, Redux prensiplerine tam bağlılık ve maksimum kontrol isteyenler NgRx’i tercih edebilir.

Sonuç

Angular uygulamalarında durum yönetimi, uygulamanın karmaşıklığı arttıkça hayati bir rol oynar. NGXS, modern Angular geliştiricilerine güçlü, ölçeklenebilir ve bakımı kolay durum yönetimi çözümleri sunan mükemmel bir kütüphanedir. TypeScript ve RxJS’in gücünü kullanarak, az boilerplate kod ile temiz ve okunabilir bir kod tabanı oluşturmanıza olanak tanır.

Bu makalede ele aldığımız temel kavramlar, kurulum, state, action, selector ve asenkron işlem yönetimi gibi konular, NGXS ile durum yönetimine başlamak için sağlam bir temel oluşturmaktadır. Plugin desteği ve modüler yapısı sayesinde büyük ve karmaşık uygulamaların bile durumunu etkin bir şekilde yönetebilirsiniz. NGXS, geliştirme sürecini hızlandırırken, uygulamanızın performansını ve sürdürülebilirliğini artırmaya yardımcı olan değerli bir araçtır. Gelecek Angular projelerinizde durum yönetimi ihtiyacınız olduğunda NGXS’i değerlendirmeniz, size önemli avantajlar sağlayacaktır.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Exit mobile version