0.0.19 • Published 1 year ago

agsm v0.0.19

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

AGSM

AGSM stands for Angular Global State Management and is a RxJs implementation of Redux concepts for Angular applications.

Table of Contents

Features

  • All application states stored in one global store
  • Read-only states (Can only be modified using pre-defined actions describing the change of events)
  • All changes are done with pure functions (reducers)
  • Clean code and a trackable file hierarchy consisting of actions, reducers and action types

Installing

npm install agsm

Example

We're going to use JSON Placeholder as a quick backend to serve data about posts/articles.

Directory tree

│   app.component.css
│   app.component.html
│   app.component.spec.ts
│   app.component.ts
│   app.module.ts
│
└───agsm
    │   store.service.ts
    │   types.ts
    │
    ├───actions
    │       post-actions.service.ts
    │
    └───reducers
            postsReducer.ts

store.service.ts

import { Injectable } from "@angular/core";
import { AgsmService } from "agsm";
import { postsReducer } from "./reducers/postsReducer";

// Store is better as a standalone service containing
// all the reducers you want to add to the tree

@Injectable({
  providedIn: "root",
})
export class StoreService {
  constructor(private agsm: AgsmService) {
    // Use this line at the beginning of the store constructor to activate agsm devtools debugging
    this.agsm.linkDevTools(true);

    // Add reducer takes in a string for the reducer name and the reducer pure function as a 2nd parameter
    this.agsm.addReducer("postsList", postsReducer);
  }
}

types.ts

export const POSTS_REQUEST = "POSTS_REQUEST";
export const POSTS_SUCCESS = "POSTS_SUCCESS";
export const POSTS_FAIL = "POSTS_FAIL";

post-actions.service.ts

import { Injectable } from "@angular/core";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { POSTS_FAIL, POSTS_SUCCESS, POSTS_REQUEST } from "../types";
import { AgsmService } from "agsm";

const httpOptions = {
  headers: new HttpHeaders({
    "Content-Type": "application/json",
  }),
};

@Injectable({
  providedIn: "root",
})
export class PostActionsService {
  // agsm will need to be injected in every action that depends on the global store for altering/retrieving states
  constructor(private http: HttpClient, private agsm: AgsmService) {}

  async getPosts() {
    // Dispatching an event is as simple as sending the UNIQUE event name as a string for the first parameter
    // and an optional payload as the second parameter
    this.agsm.dispatch(POSTS_REQUEST);

    try {
      const posts = await this.http
        .get<any>("https://jsonplaceholder.typicode.com/posts", httpOptions)
        .toPromise();

      this.agsm.dispatch(POSTS_SUCCESS, posts);
    } catch (e: any) {
      // Handled depending on backend or general error handlers.
      this.agsm.dispatch(POSTS_FAIL, e.message);
    }
  }
}

postsReducer.ts

import { POSTS_FAIL, POSTS_SUCCESS, POSTS_REQUEST } from "../types";

// Reducers are pure functions that take in an action object containing a type and a payload as a first parameter
// and the previous state of the reducer as a second parameter to return a brand new state
export const postsReducer = (
  action: any,
  state: any = {
    loading: true,
    posts: [],
    error: null,
  }
) => {
  // Switch statement to determine which state to return based on UNIQUE action types
  switch (action.type) {
    case POSTS_REQUEST:
      return { ...state, loading: true };
    case POSTS_SUCCESS:
      return {
        loading: false,
        posts: action.payload,
        error: null,
      };
    case POSTS_FAIL:
      return {
        loading: false,
        posts: [],
        error: action.payload,
      };
    default:
      return state;
  }
};

app.component.ts

import { Component, OnInit, OnDestroy } from "@angular/core";
import { Subscription } from "rxjs";
import { AgsmService } from "agsm";
import { PostActionsService } from "./agsm/actions/post-actions.service";
import { StoreService } from "./agsm/store.service";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit, OnDestroy {
  postsListSubscription: Subscription;
  postsList: any; // This will hold our state value locally in this component, it will get updated whenever the state changes

  constructor(
    private store: StoreService, // This needs to be injected first and once only in app.component.ts
    private agsm: AgsmService, // This will be injected in every component that needs the agsm store and its functionality
    private postActions: PostActionsService
  ) {}

  ngOnInit(): void {
    // Subscribing to a reducer to retrieve latest updates from actions/dispatches
    this.postsListSubscription = this.agsm
      .stateSelector((state) => state.postsList) // Selecting our reducer using a pure selector function
      .subscribe((stateValue) => {
        // Subscribing to a state will return it's current value and any subsequent updated values
        this.postsList = stateValue;
        console.log(this.postsList);
      });

    // Use our getPosts action to call our backend and update the postsList reducer/state
    // which will then emit an event to our subscriber(s) of that reducer to handle the returned data
    this.postActions.getPosts();
  }

  ngOnDestroy(): void {
    this.postsListSubscription.unsubscribe(); // To avoid memory leaks
  }
}

AGSM API

addReducer

Adds a new Reducer/state to the global store

addReducer(reducerName: string, reducerFunc: (action: Action, initialState?: any) => {}): void

dispatch

Dispatches actions to the store for reducers to change the state accordingly

dispatch(actionType: string, payload?: any): void

stateSelector

Selects a state from the global store using a selector function (e.g. (state) => state.myState)

stateSelector(selector: (state: any) => any): Observable<any>

setStoreInitialState

Sets the initial state of the store with a custom state object containing key-value pairs representing the reducer and its initial state respectively, e.g.:

mystate: {
    a: 1,
    b: 2
}

NOTE: Any state attributes provided in the state object parameter should be identical to the reducer names that were set in addReducer()

addReducer() has to be used to initialise the reducers before using this function

setStoreInitialState(state: any): void

linkDevTools

Links AGSM library to the AGSM Dev Tools extension to debug application's state

linkDevTools(activate: boolean): void

AGSM Dev Tools Extension

Check out the chrome web store and search for AGSM Dev Tools to install the current beta version

Contribution

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Credits

AGSM is heavily inspired by React Redux as an architecture/concept and is based on a RxJs implementation.

License

MIT

0.0.19

1 year ago

0.0.14

2 years ago

0.0.15

2 years ago

0.0.16

2 years ago

0.0.17

2 years ago

0.0.18

2 years ago

0.0.10

3 years ago

0.0.11

3 years ago

0.0.12

3 years ago

0.0.13

3 years ago

0.0.9

3 years ago

0.0.8

3 years ago

0.0.7

3 years ago

0.0.6

3 years ago

0.0.5

3 years ago

0.0.4

3 years ago

0.0.3

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago