0.2.3 β€’ Published 5 months ago

ngx-signal-flow v0.2.3

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

πŸš€ ngx-signal-flow

Welcome to ngx-signal-flow, a powerful state management library for Angular applications! 🌟

πŸ“– Overview

ngx-signal-flow is a lightweight and efficient state management library designed to simplify state handling in your Angular applications. It leverages RxJS and Angular’s reactive programming capabilities to provide a seamless and scalable solution for managing your application’s state.

✨ Features

  • πŸ”„ Reactive state management
  • πŸ› οΈ Easy integration with Angular signals
  • ✏️ Using Immer for state mutation
  • πŸ”’ Type-safe state management
  • πŸ“¦ Minimal boilerplate code
  • ⚑ High performance with RxJS
  • βœ… Comprehensive unit tests

πŸ“¦ Installation

To install ngx-signal-flow, run the following command in your Angular project:

npm install ngx-signal-flow

πŸš€ Getting Started

Here’s a quick guide to get you started with ngx-signal-flow:

1. Define your state

type AppState = {
  count: number;
};

2. Create your store, sources, reducers and selectors (signals)

import { Injectable } from '@angular/core';
import { createStore } from "ngx-signal-flow";

@Injectable({
   providedIn: 'root'
})
export class AppStore {
   private readonly store = createStore<AppState>({ count: 0 });

   // SOURCES
   readonly increment = this.store.source<number>();
   readonly decrement = this.store.source<number>();

   // SELECTORS
   readonly count = this.store.select('count');

   constructor() {
      // REDUCERS
      this.increment.reduce((draft: AppState, value: number) => {
         draft.count += value;
      });

      this.decrement.reduce((draft: AppState, value: number) => {
         draft.count -= value;
      });
   }
}

3. Use the store in your components

import {Component, inject} from '@angular/core';
import {AppStore} from './app.store';


@Component({
   selector: 'app-root',
   standalone: true,
   template: `
       <button (click)="store.increment(1)">Increment</button>
       <button (click)="store.decrement(1)">Decrement</button>
       <p>Count: {{ store.count() }}</p>
   `
})
export class AppComponent {
   store = inject(AppStore);
}

4. Enjoy reactive state management in your Angular application! πŸŽ‰

This is just a basic example to get you started. You don't need to use a store class, you can als use the store directly in your components, because it's functional no need to extend a class.

πŸ“š Deep Dive

πŸ“¦ Store

The store is the central piece of ngx-signal-flow. It holds your state and provides methods to interact with it.

Creating a Store - createStore

To create a store, use the createStore function with initial state as an argument.

import { createStore } from "ngx-signal-flow";

const store = createStore<State>({ count: 0 });

Store as Observable

The store is an observable that emits the state whenever it changes. You can subscribe to the store to get the state.

store.asObservable().subscribe((state: State) => {
  // handle state changes
});

Selecting Store State - select

To access the state of the store, use the store.select method with the key of the state as an argument. It returns an angular signal, that can be used in the template or in the component.

const count = store.select('count');

// use
{{ count() }}

Selectin Store State - compute

To compute a value from the state of the store, use the store.compute. It takes a function that computes the value from the state as an argument. It returns an angular signal, that can be used in the template or in the component. You can also use multiple state values to compute a value.

const doubleCount = store.compute('count', (count: number) => count * 2);
const fullName = store.compute('firstName', 'lastName', (firstName: string, lastName: string) => `${firstName} ${lastName}`);

// use
{{ doubleCount() }}
{{ fullName() }}

Modify Store State - reduce

To modify the state of the store, use the store.reduce method with a reducer function as an argument.

store.reduce((draft: State) => {
  draft.count  = draft.count + 1;
});

You call also use sources to modify the state

store.reduce(source, (draft: State, value: number) => {
   draft.count = draft.count + value;
});

store.reduce(source1, source2, (draft: State, val1: number, val2: string) => {
   draft.count = draft.count + val1;
   draft.name = val2;
});

Perform Side Effects - effect

To perform side effects based on the state of the store, use the store.effect method with an effect function as an argument. It is not like other effects, it will be executed every time the state changes.

store.effect((state: State) => {
  console.log('State changed:', state);
});

State History - undo, redo

To undo or redo state changes, use the store.undo and store.redo methods. First initialize the store with the createStore function with the withPatches option set to true. Use the store.undo and store.redo methods to undo or redo state changes.

const store = createStore<State>({ count: 0 }, { withPatches: true });
store.reduce((draft: State) => {
  draft.count  = draft.count + 1;
});
// store.count === 1
store.canRedo(); // false
store.canUndo(); // true
store.undo();
// store.count === 0
store.canUndo(); // false
store.canRedo(); // true
store.redo();
// store.count === 1
store.canRedo(); // false
store.canUndo(); // true

πŸ“‘ Sources

Sources are signals that emit values to the store. Sources are created using the store.source method. To emit a value, you can call the source as a function with the value as an argument.

// no initial value
const source = store.source<number>()
// with initial value
const source = store.source(0)
// emit a value
source(1)

Modify Store State - reduce

To modify the state of the store, use the source.reduce method with a reducer function as an argument. The emitted value is passed as an argument to the reducer function.

source.reduce((draft: State, value: number) => {
  draft.count  = draft.count + value;
});

Perform Side Effects - effect

To perform side effects based on the values emitted by sources, use the source.effect method with an effect function as an argument. It must return an observable. Effect is lazy, it will only be executed when you actually use it to reduce the state.

source.effect((value: number) => {
  return http.get(`https://api.example.com/${value}`);
});

πŸ’₯ Effects

Effects are functions that perform side effects based on the values emitted by sources. They are used to interact with external services, such as APIs or databases. Effects are defined using the source.effect method. To create an effect, see the example above.

Perform Side Effects - reduce

Since effects are lazy, you can use the effect.reduce method to subscribe to the effect and modify the state based on the data received from the effect.

source.effect.reduce((draft: State, data: any) => {
  // modify state based on the data received from the effect
});

Perform Side Effects - combine Sources

You can combine multiple sources to create an effect that depends on multiple sources.

const source1 = store.source<number>(0);
const source2 = store.source<string>('');

store.effect(source1, source2, (value1, value2) => {
  return http.get(`https://api.example.com/${value1}/${value2}`);
});

Convenience State Parameters

  • loading: effect.loading - returns a boolean signal that indicates whether the effect is currently running
  • error: if error occurs, it will be written to state.error

πŸ“œ License

This project is licensed under the MIT License. See the LICENSE file for more details.

πŸ’¬ Contact

For any questions or feedback, feel free to open an issue.

0.2.3

5 months ago

0.2.1

11 months ago

0.2.2

11 months ago

0.2.0

1 year ago

0.1.0

1 year ago

0.0.9

1 year ago

0.0.8

1 year ago

0.0.7

1 year ago

0.0.6

1 year ago

0.0.5

1 year ago

0.0.4

1 year ago

0.0.3

1 year ago

0.0.2

1 year ago

0.0.1

1 year ago