I wrote a Custom State Manager for Angular - STATE GUARDIAN


Introduction

The Custom State Manager, known as “STATE GUARDIAN”, is an innovative tool designed by Michael Egbo. Its primary purpose is to manage state, side effects, and more in Angular applications, ensuring a streamlined and efficient workflow.

Features

1. State Management Store

  • Manages and tracks the state of different parts of the application using the CustomStore.
  • Allows for selecting specific slices of the state for observation.

dispatch:

  • Purpose: Used to dispatch an action to update the state.
  • When to Use: Trigger a change in the application state, typically when updating the state based on user interactions or other events.
  • Example: When a user logs in, you may dispatch an action to update the user’s login status in the state.

select:

  • Purpose: Select and observe specific parts of the application state.
  • When to Use: “Listen” to changes in a specific part of the state, allowing you to react to state changes and update your component’s properties accordingly.
  • Example: Use select to watch for changes in the user’s login status or other parts of the state and take specific actions in response to those changes.

2. Effects Handling

  • Manages side effects, especially asynchronous operations like HTTP requests.
  • Updates the state based on the result of these side effects.

3. Middleware Integration

  • Intercepts actions to introduce additional behavior or side effects.
  • Useful for logging, analytics, or any other cross-cutting concerns.

4. Entity Management Utilities

  • Provides utilities to manage collections of entities efficiently.
  • Supports operations like adding one or many entities, updating, and deleting entities.

5. Data Normalization

  • Transforms nested or complex data structures into a flat, consistent format.
  • Helps in efficiently managing and querying data.

6. Caching Mechanism

  • Caches frequently accessed data to reduce the need for repeated network requests.
  • Improves performance and can provide offline data access.

7. Error Handling

  • Handles errors during side effects and updates the state accordingly.
  • Provides a consistent way to manage and display errors to users.

8. Flexible API Calls

  • The effects service can handle both direct HTTP calls and calls through provided services, offering flexibility in how backend interactions are managed.

9. Immutable State Updates

  • Ensures that state updates are immutable, leading to predictable state changes and easier debugging.

10. Integration with External Libraries

  • Designed to work seamlessly with libraries like RxJS, making it easier to handle asynchronous operations and reactive programming patterns.

11. Modularity and Reusability

  • Components like the store, middleware, and effects are modular, allowing for reuse across different parts of the application or even different applications.

State Guardian Library Documentation

State Guardian is a powerful state management library for Angular applications. It provides various features for managing application state, handling asynchronous effects, managing entities, normalizing data, caching, and more. This documentation will guide you through the installation and usage of State Guardian.

Installation

To install the state-guardian library in your Angular project, you can use npm or yarn:

npm install state-guardian        

or


yarn add state-guardian        


Usage

After installation, you should import and add the StateGuardianModule to your application's module. Here's an example:


import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StateGuardianModule } from 'state-guardian';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, StateGuardianModule],
  bootstrap: [AppComponent],
})
export class AppModule {}        

Example Use Case: Book Management App

  1. Login: Use the EffectsService for login and set user tokens upon success.
  2. Fetch Books: After logging in, fetch the books. Use normalization to flatten the data and cache it for faster subsequent access.
  3. Manage Books: Add, update, or delete books using the EntityManagementService.
  4. Middleware: Log actions using the middleware, providing insights into the user’s behavior.

1. State Model

Define the state model for our application:

interface AppState {
  user: User;
  books: Book[];
}

interface User {
  id: number;
  username: string;
  token: string;
}

interface Book {
  id: number;
  title: string;
  author: string;
}        


2. Stub Backend Service

Simulate the login process:

@Injectable({
  providedIn: 'root'
})
export class StubBackendService {
  private users = [
    { id: 1, username: 'user1', password: 'pass1', token: 'fake-token' }
  ];
  private books = [
    { id: 1, title: 'Book 1', author: 'Author 1' },
    { id: 2, title: 'Book 2', author: 'Author 2' }
  ];

  login(credentials: { username: string; password: string }): Observable<User> {
    const user = this.users.find(u => u.username === credentials.username && u.password === credentials.password);
    if (user) {
      return of(user);
    } else {
      return throwError('Invalid credentials');
    }
  }

  getBooks(): Observable<Book[]> {
    return of(this.books);
  }

  addBook(book: Book): Observable<Book> {
    this.books.push(book);
    return of(book);
  }
}        

3. Login Component

Create the login component that uses the EffectsService and CustomStore:

import { CustomStore, EffectsService } from 'state-guardian';

@Component({
  // ... component metadata
})
export class LoginComponent {
  form: FormGroup;
  private LOGIN_INITIAL_STATE: LoginState = {
    user: {
      isLoggedIn: false,
      tokens: {
        AccessToken: null,
        RefreshToken: null,
      },
      error: null,
      isLoading: false,
    }
  };
  constructor(
    private authService: AuthService,
    private store: CustomStore<AppState>,
    private effectsService: EffectsService
  ) {this.store.dispatch(() => this.LOGIN_INITIAL_STATE)}

  ngOnInit() {
  // Subscribe to user state changes
  this.store.select('user').subscribe(userState => {
    this.isLoading = userState.isLoading;
    if (userState.isLoggedIn) {
      this.router.navigateByUrl('/home');
    }
    if (userState.error) {
      Swal.fire('Hi...', 'Kindly check your username or password', 'warning');
    }
  });
  }

  onSubmit() {
    const credentials = this.form.value;

    const loginEffectConfig = {
      apiCall: () => this.authService.login(credentials),
      successHandler: (response: User) => {
        this.store.dispatch(() => ({ user: response }));
      },
      errorHandler: (error) => {
        console.error('Login failed:', error.message);
      }
    };

    this.effectsService.handleEffect(loginEffectConfig);
  }
}        

4. Book Management Component

Handle fetching and managing books:

import { CustomStore, EntityManagementService } from 'state-guardian';

@Component({
  // ... component metadata
})
export class BookManagementComponent implements OnInit {
  books: Book[];

  constructor(
    private store: CustomStore<AppState>,
    private bookService: BookService,
    private entityService: EntityManagementService
  ) {}

  ngOnInit() {
    this.books = this.bookService.getBooks();
    this.store.dispatch(() => ({ books: this.books }));
  }

  addBook(newBook: Book) {
    this.entityService.addOne('books', newBook);
  }

  updateBook(updatedBook: Book) {
    this.entityService.updateOne('books', updatedBook.id, updatedBook);
  }

  deleteBook(bookId: number) {
    this.entityService.deleteOne('books', bookId);
  }
}        

6. Middleware Integration

Use the middleware to log actions:

import { MiddlewareService } from 'state-guardian';

@Injectable()
export class LoggingMiddleware {
  constructor(private middlewareService: MiddlewareService) {}

  logAction(actionType: string, payload: any) {
    this.middlewareService.intercept(actionType, payload);
  }
}        

7. Data Normalization

When fetching nested data, use normalization:

import { NormalizationService } from 'state-guardian';

@Injectable()
export class BookService {
  constructor(private normalizationService: NormalizationService) {}

  getBooks() {
    const nestedBooks = [
      { id: 1, title: 'Title 1', author: { name: 'Author 1' } },
      { id: 2, title: 'Title 2', author: { name: 'Author 2' } }
    ];

    return this.normalizationService.normalize(nestedBooks, 'id');
  }
}        

This documentation gives a more in-depth overview of how to use the state manager and its features. Adjust paths, method names, and data structures to fit your actual implementation.

Author: Michael Egbo Email: https://www.dhirubhai.net/in/michaelegbo/ Package — https://www.npmjs.com/package/state-guardian

TO Contribute see — https://github.com/michaelegbo/State-Guardian




要查看或添加评论,请登录

Mike Egbo的更多文章

  • MMM And lies people told themselves

    MMM And lies people told themselves

    For those who lost hard earned money after being deceived I TRULY FEEL FOR YOU. In all honesty, some may have…

    17 条评论
  • Your new website should take time to build

    Your new website should take time to build

    As you consider your new website, one of the questions that you’re sure to ask is; ‘how long is this going to take?’…

  • Twelve rules for developing more secure Java code

    Twelve rules for developing more secure Java code

    Writing security-conscious Java code can help you avoid security surprises This article introduces 12 rules for writing…

    2 条评论

社区洞察

其他会员也浏览了