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
dispatch:
select:
2. Effects Handling
3. Middleware Integration
4. Entity Management Utilities
5. Data Normalization
6. Caching Mechanism
7. Error Handling
8. Flexible API Calls
9. Immutable State Updates
10. Integration with External Libraries
11. Modularity and Reusability
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. 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