Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Angular – NgRx Kütüphanesi İle State Management

Merhaba,

UI tabanlı geliştirilen Single Page Application(SPA) uygulamalarında ziyaretçilere masaüstü yazılımlarına yakın bir dinamizm ile eşlik edilirken, bu avantajın bir yandan da dezavantajı olabilecek olan sayfada tutarsız verilerin olması durumlarıyla sık sık karşılaşılabilmektedir. Bu tutarsız verilerden kastedileni yazının ilerleyen paragraflarında daha da açacak olsak dahi kısaca bahsetmemiz gerekirse eğer; misal olarak uygulama ekranında sepette üç ürün gözüküyorken, metinsel kısımda ise bu sayı sepettekiyle tutarlılık göstermiyor olabilir. Ya da sepete yeni bir ürün eklendiğinde veya çıkarıldığında biryandan metinsel kısmın duruma göre güncellenmesi beklenir. Ama bazen olmayabilir. Olmayabilir, çünkü uygulama geliştirme sürecinde kullanılan pattern’ın bizlere sağlayabildiği kadar güncellemelere özen gösterilse dahi bir yerden sonra uygulama kompleksitesi artarak, bu güncellemeleri yapmak ve yönetmek zorlaşabilir. İşte bu tarz, uygulama üzerinde tutarlılığı ilgilendiren ve bir verinin bütünselliği açısından yönetilebilirliği sağlamak için geliştirilen genel stratejiye State Management denmektedir. Şimdi gelin bu kavram nedir, kökleri nerelere dayanmaktadır ve bizlere çözüm olarak ne gibi yapılar sunmaktadır hep beraber inceleyelim.

Herşeyden önce, State Management’i anlayabilmek için State kavramını anlamak gerekmektedir. Bizler, önceden kaleme almış olduğumuz Finite-State Machine Nedir? başlıklı makalemizde bir program/yazılım için state’in ne manaya geldiğine değinmiştik. İlgili makaleye göz atmanızı önermekle birlikte yine de bu içeriğimizde konuyu aydınlatabilmek için ayrı bir dille izah etmeye çalışacağız.

State Nedir?

Bir uygulama için state, o uygulamanın tüm belleğidir diyebiliriz. Yani, uygulama sürecindeki herhangi bir veri o anki state’i temsil etmektedir.

İşte state bu! Peki State Management nedir? diye sorduğunuzu duyar gibiyim…

State Management Nedir?

Uygulamadaki verilerden(yani state’lerden) herhangi biri değişkenlik gösterdiği taktirde, bu veriyi/state’i kullanan tüm noktalarda bu değişikliğin yansıması gerekmektedir. İşte bu state’lerin(yani verilerin) kullanıldığı farklı noktalar üzerinde anlık değişimlerin yansımasına ise State Management denmektedir.

En yalın haliyle State Management’ında ne olduğunu öğrendikten sonra muhtemelen aklınızdaki Peki hoca, NgRx kütüphanesi nedir? misali sualinizi cevaplandırmaya çalışalım…

NgRx Kütüphanesi Nedir?

NgRx kütüphanesini anlayabilmek için Redux’ı, Redux’ı anlayabilmek için ise Flux Pattern’i anlamamız gerekmektedir. Haliyle, konuyu net kavrayabilmek ve özümseyebilmek için en temelden ele almamız gerekmektedir. Eee hadi o zaman, fazla vakit kaybetmeden, buyrun başlayalım…

İlk etapta konuyu Angular mimarisi bazında ele alarak başlayacağız. Biliyorsunuz ki, tipik bir Angular uygulaması birçok bileşenden meydana gelecek şekilde tasarlanabilmekte ve uygulamadaki bu bileşenlerin her biri, diğer bileşenlerin haberi olmayacak şekilde kendilerine has state’ler barındırabilmektedir. Bu bileşenlerin, bir araya gelerek meydana getirdikleri uygulamada nihai olarak bütünsel anlamda bir uygulama verisi söz konusu olmaktadır. İşte bu durumda, bileşenlerden herhangi birinde oluşacak state değişimi, bütünsel anlamda o state’i kullanan diğer bileşenlere de yansıtılması gerekmektedir. Yani anlayacağınız birden çok bileşen bir araya geldiği zaman, sayfa tutarlılığı açısından bu bileşenlerdeki state’lerin yönetilmesi gerekmektedir.

Angular - NgRx Kütüphanesi İle State ManagementEsasında, bileşenler arasında state değişiklikleri için gerekli haberleşmeyi parent ve child seviyesinde @Input ve @Output tanımları aracılığıyla gerçekleştirebilmekteyiz. Bu konuya dair gerekli teferruat için Angular’da @Input ve @Output Değişkenler İle Componentler Arası İletişim başlıklı makaleyi okumanız yeterli olacaktır.
Angular - NgRx Kütüphanesi İle State ManagementBir bileşendeki değişikliği @Input veya @Output tanımları aracılığıyla diğer bileşene iletmek, yandaki şemada görüldüğü üzere makul sayıdaki bileşenlerin kullanıldığı senaryolar için geçerli olacaktır. Angular - NgRx Kütüphanesi İle State ManagementKullanılan bileşenlerin sayısı sağdaki şemada olduğu gibi arttıkça, @Input ve @Output ile state bilgilerinin iletilmesi bildiğiniz kabus haline gelecektir. Misal olarak, ‘Component 3’den ‘Component 6’ya bir veri aktarmamız gerekiyorsa eğer bunu adı geçen yöntemlerle uygulamak, deveye hendek atlattırmakla eşdeğerdir. Nihayetinde bu uygulama bir gayeye hizmet etmek için geliştirilirken, maliyetin büyük çoğunluğu bu yüksek kompleksiteye sahip operasyon için harcanıyor olacaktır.

İşte bu durum klasik bir State Management problemi ortaya koymaktadır.

Bu klasik State Management probleminin daha da tarihsel bir mazisi var mı?

Tabi ki de! Özünde bu tarz State Management problemini ilk olarak Facebook‘un yaşadığına dair söylentiler mevcut. Hatta bir zamanlar Facebook’un chatbox’ında ki verilerle mesaj sayfasındakilerin pek tutarlılık göstermediğini bizzat tecrübe etmişliğim vardır(gerçi şimdiki haline dairde bir fikrim yok) İşte bu durum state management ile ilgilidir. Facebook, aynı verileri kullanacak şekilde zamanla eklenen ekstra bileşenlerin eşliğinde uygulamadaki state management kompleksitesinin arttığını söylemekte ve bunun sebebini kullandığı klasik veri/model yaklaşımına yüklemektedir.

Two Way Data Binding(İki Yönlü Veri Bağlama)

Bu modelde view’ın render edilmesi modeldeki değişiklik üzerine kurgulanmıştır. Halbuki bir view’deki modelde yapılan değişiklik, o modeli kullanan diğer bir view(ler)’e yansımadığı sürece tutarsızlık söz konusu olacaktır.
Burada view’den kasıt component’lerdir.

Haliyle Facebook buradaki two way databinding modeli yerine bir yerden sonra daha gelişmiş bir stratejiyi modellemektedir, Flux Pattern!

Flux Pattern Nedir?

Angular - NgRx Kütüphanesi İle State Management

Kaynak : https://krasimir.gitbooks.io/react-in-patterns/content/chapter-08/


Flux Pattern, yukarıdaki şemada görüldüğü üzere view’de olabilecek herhangi bir state değişikliğini ifade eden action isimli talimatı dispatcher’a göndererek, onun aracılığıyla store’lara bildirmektedir.

Peki nedir bu action, dispatcher ve store?

  • Action; state’de yapılan değişikliği tarif edecek olan gövdeyi barındıran bir talimattır. Yapısı aşağıdaki gibidir;
    Angular - NgRx Kütüphanesi İle State Management
  • Dispatcher; ise kendinde tanımlı olan tüm store’lara gelen action’ı yayınlamakla görevlidir ve state’de yapılan değişikliğe dair haber almalarını sağlamaktadır. Yani, yayınlanan action, dispatcher’a gönderilmektedir.
    Angular - NgRx Kütüphanesi İle State Management
  • Store; state’in tutulduğu yapılardır. Store’da, ilgili state’e dair bir değişiklik yapılacaksa bu sorumluluğu Reducer isimli fonksiyonlar üstlenmektedir. Bu fonksiyonları yazımızın ilerleyen satırlarında ele alıyor olacağız…

Yukarıdaki döngü işlediği an, state’de değişiklik gerçekleşecek ve artık ilgili state’i kullanan tüm view’lerin haberdar edilmesi gerekecektir. Haliyle bu işlemi de Controller View ismini verdiğimiz mekanizma üstlenecektir.
Angular - NgRx Kütüphanesi İle State ManagementController View, yandaki şema da görüldüğü üzere uygulamadaki tüm view’ler ile haberleşen bir nesnedir. Eğer ki store’da herhangi bir state verisinde bir değişiklik olursa buna göre view’leri render edecektir.

İşte Flux Pattern bu şekilde bir mantıkla uygulanmaktadır. Dikkat ederseniz eğer uygulama yapısı büyüdükçe ve uygulamanın kompleksitesi arttıkça bu pattern’ın uygulanması zorlaşmaktadır. Çünkü birden fazla store’un kullanılması, zamanla uygulama büyüklüğüyle doğru orantılı bir şekilde birbirlerine bağımlı olabilmelerine sebep olmakta ve bunların orkestra edilmesi git gide zorlaşmaktadır.

Bu noktada bu pattern’in daha rahat uygulanmasını sağlayan Redux karşımıza çıkmaktadır.

Redux Nedir?

Angular gibi JavaScript temelli uygulamalarda State Management süreçlerini basitleştirmek için kullanılan pattern’dır. Temel olarak Single source of truth​, State is read-only​ ve Changes are made with pure functions​ olmak üzere üç ilkeye dayanmaktadır. Direkt ilkeleri izah etmemiz gerekirse eğer;

  • Single source of truth​ Tek doğru kaynak​
    Uygulamada state’in tek bir store’da tutulmasını ifade etmektedir.
  • State is read-only Salt-okunur durum
    Store’da ki mevcut state verilerinin değişmezliğini ifade eder. Yani, state’de olan değişiklik o anki store’da ki veri üzerinde değil, onun klonlanmış hali üzerinde uygulanmalıdır.
  • Changes are made with pure functions​ Değişiklikler saf fonksiyonlarla yapılır
    Store’lar üzerindeki değişikliklerin sadece o işe odaklanmış reducer ismini verdiğimiz fonksiyonlar ile yapılmasını ifade eder.

Angular - NgRx Kütüphanesi İle State Management

Kaynak : https://blog.mazarin.lk/productive-development-react-redux/

Redux’u daha iyi anlayabilmek için yandaki görsel şemayı incelemenizde fayda görmekteyim.

Dikkat ederseniz eğer Redux tek bir store’dan beslenmekte ve tüm component’lere o store üzerinden veri sağlamaktadır. Redux’un kullanılmadığı durumlarda ise bir arapsaçı misali davranış sergilenmektedir.

Redux’da state, read-only’dir. Değişmez! (Flux pattern’da değişiklik oluyordu!) Redux’da immutability prensibi söz konusudur. State değişecekse eğer mevcut olan kopyalanıp, üzerinde değişiklik gerçekleştirilerek işlem sağlanır.

Peki Redux pattern’ın çalışma mantığı nasıldır?
Redux pattern’ın çalışma mantığını anlayabilmek için aşağıdaki görsel şemayı incelemenizi tavsiye ederim.

Angular - NgRx Kütüphanesi İle State Management

Kaynak : https://labs.tadigital.com/index.php/2020/04/20/getting-started-with-redux/

Şemadan görüldüğü üzere, UI’da(yani view’de) herhangi bir state değişikliği söz konusu olduğunda bu değişikliği temsil eden bir action fırlatılmakta ve fırlatılan bu action’ı reducer fonksiyonu ile yakalayarak yapılan değişiklik algılanmakta ve store’da ki mevcut state yerine gerekli güncellemelerin yapıldığı yeni state oluşturularak, store’a gönderilmektedir. Bu işlem neticesinde, store’da yenilenen state verisi ilgili tüm view’ler tarafından güncellenmiş olacaktır.

Konuyu teoride daha da netleştirebilmek için aşağıdaki hareketli görseli inceleyiniz.

Angular - NgRx Kütüphanesi İle State Management

Kaynak : https://ichi.pro/tr/redux-icin-crash-course-98889338959570

Evet… NgRx ile ilgili tüm altyapıyı incelediğimize göre artık sadede gelebiliriz. NgRx Nedir? sorusunu cevaplayabiliriz.

NgRx, Redux Pattern’i benimsemiş ve RxJS kütüphanesiyle desteklenmiş olan Angular’a özel State Management çözümleri için geliştirilmiş bir kütüphanedir.

Şimdi, sonlara doğru NgRx ile basit bir örnek üzerinden içeriğimizi zenginleştirip, noktalayalım.

NgRx Kütüphanesi İle Örnek State Management Uygulaması

İcra edeceğimiz örnekte bir ders uygulamasını baz alıyor olacağız. Bu uygulamada dersleri eklediğimiz, listelediğimiz ve ders adedini yayınladığımız üç farklı componentimiz olacaktır. Haliyle ders sayısında herhangi bir değişiklik olduğu taktirde listeleme ve adedi göstermekten sorumlu componentlerin gerekli güncellemeleri yapabilmesi için NgRx ile state management’ı sağlıyor olacağız.

Her şeyden önce uygulamada kullanacağımız kütüphaneleri aşağıdaki npm komutu eşliğinde yükleyerek başlayalım.
npm i jquery bootstrap @ngrx/store
(Angular uygulamalarında NgRx kütüphanesini @ngrx/store paketi sağlamaktadır.)

Ardından uygulamada kullanılacak componentleri oluşturalım.
ng g c components/lessonCreate
ng g c components/lessonRead
ng g c components/lessonCount

Şimdi hafiften kodlamaya girerek başlayalım.

‘app.component.html’ dosyasında uygulamanın genel HTML çatısını aşağıdaki gibi oluşturalım. Tabi bu konuda sizler kendi özgür iradenize göre çalışma sergileyebilirsiniz. Ben deniz, hasbel kader aşağıdaki gibi (kıytırıktan)bir tasarım üzerinden yürüyor olacağım.

<div class="row">
  <div class="col-md-3">Ders Ekle</div>
  <div class="col-md-3">Ders Listesi</div>
  <div class="col-md-3">Ders Adedi</div>
</div>
<div class="row">
  <div class="col-md-3" style="background-color: antiquewhite;">
    <app-lesson-create></app-lesson-create>
  </div>
  <div class="col-md-3" style="background-color: darkgrey">
    <app-lesson-read></app-lesson-read>
  </div>
  <div class="col-md-3" style="background-color: ghostwhite">
    <app-lesson-count></app-lesson-count>
  </div>
</div>

Ardından uygulamada verisel açıdan bir derse karşılık gelecek olan ‘Lesson’ isimli entity’i oluşturalım.

export class Lesson {
    id: number;
    lesson: string;
    explanation: string;
}

Bu noktadan itibaren uygulamamızın temellerini atmış bulunmaktayız. Artık yapılması gereken NgRx enstrümanlarını tek tek inşa etmektir. Hadi buyrun başlayalım.

  • Adım 1 Action Oluşturma
    NgRx açısından ilk yapılması gereken iş, state değişikliklerinin gövdesini tanımlamaktır. Yani action’ları tanımlamak! Bunun için ‘@ngrx/store’ altındaki ‘createAction’ fonksiyonunu aşağıdaki gibi kullanabilirsiniz.

    import { createAction, props } from "@ngrx/store";
    import { Lesson } from "src/app/models/lesson";
    
    //Action Type
    export const ADD_LESSON = '[ADD LESSON] Lesson';
    
    //Action Type
    export const REMOVE_LESSON = '[REMOVE LESSON] Lesson';
    
    //Action
    export const addLesson = createAction(ADD_LESSON, props<Lesson>());
    
    //Action
    export const removeLesson = createAction(REMOVE_LESSON, props<Lesson>());
    

    Yukarıdaki kod bloğunu incelerseniz eğer ‘addLesson’ ve ‘removeLesson’ olmak üzere iki adet action tanımlanmıştır. Bu action’lar sırasıyla, ders ekleme ve ders silme durumlarına karşılık gelmektedirler.

  • Adım 2 Reducer Oluşturma
    Action’lar tanımlandıktan sonra fırlatılan action’a göre store’da ki state’de değişiklik yapacak olan reducer fonksiyonlarını oluşturmamız gerekmektedir. Bunun için de ‘@ngrx/store’da ki ‘createReducer’ fonksiyonu kullanılabilir.

    import { createReducer, on } from "@ngrx/store";
    import { Lesson } from "src/app/models/lesson";
    import { addLesson, removeLesson } from "../actions/lesson.action";
    
    //State Type
    export interface IState {
        data: Lesson[];
    }
    
    //Initial State
    export const initialLessonState: IState = {
        data: Array<Lesson>()
    }
    
    //Reducer
    export const lessonReducer = createReducer(initialLessonState,
        on(addLesson, (state, lesson) => {
            const newState: IState = {
                data: [...state.data, lesson]
            }
            return newState;
        }),
        on(removeLesson, (state, lesson) => {
            const deleteLesson = state.data.find(l => l.id == lesson.id);
            const deleteLessonIndexNo = state.data.indexOf(deleteLesson);
            const newState: IState = {
                data: [...state.data]
            };
            newState.data.splice(deleteLessonIndexNo, 1);
            return newState;
        })
    );
    

    Yukarıdaki kod bloğunu siz incelerken bir yandan da ben izah edersem eğer ilk olarak 6. satırdaki interface’den başlayalım. Bu interface ders için tutulacak state’in arayüzünü ya da bir başka deyişle tipini ifade etmektedir. 11. satırdaki ‘initialLessonState’ nesnesine gelirsek eğer uygulamadaki default ders state’ini ifade etmektedir. State management operasyonlarında hiçbir state olmasa da default bir state’in olması zorunludur! 16 ile 32. satır aralığına göz atarsanız eğer, işte bu bizim reducer fonksiyonumuzdur. Bu fonksiyon içerisinde her bir action’a özel gerekli operasyonlar yürütülmektedir. Bu operasyonları da yine ‘@ngrx/store’ içerisindeki ‘on’ fonksiyonu ile gerçekleştirebilmekteyiz.

  • Adım 3 AppModule’e Bildiride Bulunma
    Son olarak yapılması gereken ise NgRx ile ilgili sağlanan tüm bu konfigürasyonları Angular uygulamasının ana modülü olan AppModule sınıfına bildirmektir.

    .
    .
    .
    import { StoreModule } from '@ngrx/store';
    import { lessonReducer } from './stateManagement/reducers/lesson.reducer';
    
    @NgModule({
      declarations: [
        AppComponent,
        LessonCreateComponent,
        LessonReadComponent,
        LessonCountComponent
      ],
      imports: [
        BrowserModule,
        AppRoutingModule,
        StoreModule.forRoot({
          lesson: lessonReducer
        })
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    

    Görüldüğü üzere AppModule içerisinde ‘imports’ alanında ‘StoreModule.forRoot’ fonksiyonu ile oluşturmuş olduğumuz ‘lessonReducer’ isimli reducer’imiz uygulamaya ‘lesson’ key’i karşılığında tanımlanmış bulunmaktadır. Tabi uygulama sürecinde birden fazla reducer varsa eğer tüm bunlar kendilerine özel key’ler karşılığında tanımlanıp, uygulama açısından bildirilmeleri gerekmektedir.

NgRx’in enstrümanlarını inşa ettiğimize göre artık component’ler de gerekli geliştirmeleri gerçekleştirebiliriz.

  • lesson-create.component
    .html;

    <input type="number" #txtId placeholder="Id"> <br>
    <input type="text" #txtLesson placeholder="Lesson"> <br>
    <input type="text" #txtExplanation placeholder="Explanation"> <br>
    <button (click)="createLesson(txtId.value, txtLesson.value, txtExplanation.value)">Add Lesson</button>
    

    .ts;

    import { Component, OnInit } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { Observable } from 'rxjs';
    import { Lesson } from 'src/app/models/lesson';
    import { addLesson } from 'src/app/stateManagement/actions/lesson.action';
    
    @Component({
      selector: 'app-lesson-create',
      templateUrl: './lesson-create.component.html',
      styleUrls: ['./lesson-create.component.css']
    })
    export class LessonCreateComponent implements OnInit {
    
      constructor(private store: Store<Lesson>) { }
      lesson: Observable<string>;
      ngOnInit(): void {
        this.lesson = this.store.select("lesson");
      }
    
      createLesson(id: string, lesson: string, explanation: string) {
        const l: Lesson = new Lesson();
        l.id = parseInt(id);
        l.explanation = explanation;
        l.lesson = lesson;
        this.store.dispatch(addLesson(l));
      }
    }
    

    Herhangi bir component’ten store’a erişebilmek için ‘Store<T>’ türünden nesneyi dependency injection ile talep etmemiz yeterli olacaktır. Eğer ki store’a değer gönderilecekse ‘dispatch’ fonksiyonu eşliğinde ilgili action’ın çağrılması yeterli olacaktır. Bu arada 17. satıra bakarsanız eğer ilgili store’un hangi key’e karşılık store olduğunu ‘ngOnInit’ üzerinden belirtmekteyiz.

  • lesson-read.component
    .html;

    <p *ngFor="let lesson of lessons.data">{{lesson.lesson}} - {{lesson.explanation}} &nbsp; <span
        style="color: chocolate;cursor: pointer;background-color: cornsilk;" (click)="removeLesson(lesson)">X</span> </p>
    

    .ts;

    import { Component, OnInit } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { Lesson } from 'src/app/models/lesson';
    import { removeLesson } from 'src/app/stateManagement/actions/lesson.action';
    
    @Component({
      selector: 'app-lesson-read',
      templateUrl: './lesson-read.component.html',
      styleUrls: ['./lesson-read.component.css']
    })
    export class LessonReadComponent implements OnInit {
    
      constructor(private store: Store<Lesson>) { }
      lessons: any;
      ngOnInit(): void {
        this.store.select("lesson").subscribe(lesson => {
          this.lessons = lesson;
        });
      }
    
      removeLesson(lesson: Lesson) {
        this.store.dispatch(removeLesson(lesson));
      }
    }
    
    
  • lesson-count.component
    .html;

    Lesson Count : {{count}}
    

    .ts;

    import { Component, OnInit } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { Observable } from 'rxjs';
    import { Lesson } from 'src/app/models/lesson';
    
    @Component({
      selector: 'app-lesson-count',
      templateUrl: './lesson-count.component.html',
      styleUrls: ['./lesson-count.component.css']
    })
    export class LessonCountComponent implements OnInit {
    
      constructor(private store: Store<Lesson>) { }
      lesson: Observable<string>;
      count: number;
      ngOnInit(): void {
        this.lesson = this.store.select("lesson");
        this.lesson.subscribe((lessons: any) => this.count = lessons.data.length);
      }
    }
    

İşte bu kadar…

Uygulamayı derleyip, çalıştırdığımızda aşağıdaki ekran görüntüsünde olduğu gibi davranış gösterecektir.
Angular - NgRx Kütüphanesi İle State Management

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

Not : Örnek projeyi indirmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.