Memulai Angular + NgRx

Pertama kali menggunakan NgRx saya mengalami kesulitan memahami fundamentalnya utamanya untuk versi terbaru. Dalam pos ini akan dibahas:

  • Apa itu NgRx?
  • Fundamental dari NgRx: Store, Action, Reducer, Selector dan Effect.
  • Bagaimana komponen NgRx saling berinteraksi
  • Baik dan buruk NgRx

Apa itu NgRx?

NgRx kependekan dari Angular Reactive Extensions. NgRx adalah manajemen state – terinspirasi dari Redux. Sebelum melangkah lebih jauh baiknya pahami state terlebih dahulu.

State

Secara teori state merupakan memori dari sebuah aplikasi. Singkatnya state adalah apa-apa saja yang diinput oleh pengguna atau data dari Restful API.

Aplikasi Angular dibangun dari banyak component, masing-masing component memilik state (memori) sendiri dan tidak terhubung dengan state dari component lain. Angular menyediakan fitur @Input dan @Output untuk berbagi state antara parent-child component. Gambarannya seperti ini.

Ketika jumlah component bertambah banyak mengandalkan @Input dan @Output tidak relevan. Bayangkan saja untuk men-trigger tombol di header dari footer, berapa banyak component yang dilewati?

Kita harus membuat @Input dan @Ouput disetiap component yang dilewati! Benar-benar mimpi buruk.

Redux

Redux menyederhanakan manajemen state dari aplikasi JavaScript (tidak hanya Angular dan ReactJS). Redux membawa tiga konsep utama yakni;

Single source of truth – semua state dari aplikasi disimpan dalam satu tempat (store). Store ini sebagai pusat state dari semua component seperti halnya otak. Dengan begitu interaksi state disetiap component di-salurkan-kan oleh store. Bukan component – component, melainkan component – store – component.

Read-only stateState immutable. Bukan berarti tidak boleh dirubah hanya saja tidak diizinkan melakukan perubahan secara langsung kedalam state. Semua perubahan harus dilakukan melalui action.

State is modified with pure functions – Melakukan perubahan melalui action akan men-trigger function yang disebut reducer. Reducer bertanggung jawab merubah state berdasarkan apa yang diminta oleh action. Perlu dicatat reducer selalu mengembalikan state yang sudah dirubah.

Fundamental NgRx: Store, Action, Reducer, Selector dan Effect

Store

Store merupakan pintu dari semua proses manajemen state. Store meyimpan state dan sebagai hub interaksi antar component. Menginisiasi store sebagai berikut;

constructor(private _store: Store<AppState>) {}

Store digunakan oleh dua operasi utama yakni;

  • Mengirim action ke store dengan metode store.dispatch(…), dimana akan men-trigger reducer dan effect.
  • Menerima state aplikasi melalui selector.

Struktur state dari object tree

Misalnya aplikasi mempunyai dua modul User dan Product. Setiap modul memiliki dan menangani state yang berbeda dari keseluruhan state aplikasi.

Action

Adalah sekumpulan instruksi untuk merubah store dengan metadata (atau payload, tidak wajib). Semua perubahan didalam store bergantung pada tipe action. Action dipresentasikan dalam object JavaScript dengan dua atribut utama yakni type dan payload.

{
  "type": "Login Action",
  "payload": {
    userProfile: user
  }
}

NgRx versi > 8 menyediakan utilitas bernama createAction selaku pembuat action (bukan action, tapi pembuat action). Jadi action dibuat otomatis oleh createAction. Contoh kode seperti ini;

export const login = createAction(
    "[Login Page] User Login",
    props<{user: User}>()
);

Kita bisa menggunakan login action creator untuk menciptakan action dan mengirim state kedalam store. Dalam kode dibawah ini user adalah payload yang dimasukkan kedalam state melalui action.

this.store.dispatch(login({user}));

Reducer

Bertugas merubah state dan mengembalikan state baru sesudah dirubah. Reducer memiliki dua parameter yakni state lama dan action. Berdasarkan permintaan action, reducer melakukan perubahan state lama untuk membuat dan mengembalikan state baru.

Seperti action dengan createAction, NgRx juga menyediakan utilitas pembuat reducer bernama createReducer. Umumnya penggunaan createReducer seperti ini;

export const initialAuthState: AuthState = {
    user: undefined
};

export const authReducer = createReducer(

    initialAuthState,

    on(AuthActions.login, (state, action) => {
        return {
            user: action.user
        }
    }),

    on(AuthActions.logout, (state, action) => {
        return {
            user: undefined
        }
    })

);

Boom! Semua perubahan state didalam AuthState dilakukan melalui reducer berdasarkan action type. Sampai disini bertemu “momen aha!?” jika belum baca ulang dari awal.

Effect

Merupakan “kejadian sampingan” ketika action dikirim ke store. Ketika user berhasil login dengan action type Login Action maka membawa data user di payload kedalam store. Reducer menangkap perintah action untuk merubah state user. Dalam banyak kasus kita ingin menyimpan state user secara permanen katanlah di localStorage atau server melalui Restful API. Effect bertugas melakukan penyimpanan tersebut.

NgRx menyediakan utilitas bernama createEffect (sama seperti action dan reducer). Penggunaannya seperti ini;

login$ = createEffect(() =>
        this.actions$
            .pipe(
                ofType(AuthActions.login),
                tap(action => localStorage.setItem('user',
                        JSON.stringify(action.user))
                )
            )
    ,
    {dispatch: false});

Jika dispatch bernilai true (defaultnya true) maka createEffect mengembalikan Observable<Action>. Ini digunakan hanya jika ingin men-trigger action lain (chain action). Apabila merupakan akhir dari action pastikan set ke false, karena jika tidak akan terjadi infinite loop dengan action yang sama.

Dalam contoh kode diatas kita tidak perlu melakukan action lain setelah user login. Karenanya dispatch diset ke false.

Berikut contoh chain action;

loadCourses$ = createEffect(
    () => this.actions$
        .pipe(
            ofType(CourseActions.loadAllCourses),
            concatMap(action =>
                this.coursesHttpService.findAllCourses()),
            map(courses => allCoursesLoaded({courses}))

        )
);

Skenarionya:

  1. Effect menerima action ber-type loadAllCourses.
  2. Restful API meminta data ke server.
  3. Respon dari API men-trigger action ber-type allCoursesLoaded dan memasukkan payload data (kemudian data ini disebut state) dari API ke action.
  4. Selanjutnya action allCoursesLoaded mengirim state ke store.
  5. Reducer menerima state dari action allCoursesLoaded dan merubah state lama (bisa jadi CRUD).

Selector

Digunakan untuk mengambil “potongan” state dari store state. Misalnya ingin mengambil nama pengguna dari state user. NgRx menyediakan utilitas createSelector, contoh penggunaan seperti ini;

export const isLoggedIn = createSelector(
    state => state['auth'],
    auth =>  !!auth.user
);

Untuk menangkap potongan tersebut menggunakannya seperti ini;

this.isLoggedIn$ = this.store
  .pipe(
    select(isLoggedIn)
  );

Interaksi Antar Elemen NgRx

Ilustrasi berikut menggambarkan bagaimana elemen fundamental NgRx saling berinteraksi.

Baik dan Buruk dari NgRx

Baik

  • Konsep single source of truth memudahkan mengontrol dan mengetahui bagaimana informasi saling betukar antar component.

Buruk

  • Untuk pemula sulit dipelajari.
  • Aplikasi terlihat gemuk walau sekedar merubah checkbox.