@estrellajosem/store
v0.1.0
Published
Angular-native signal-first state management. Actions + decorators from NgRx/NGXS, zero RxJS in components.
Downloads
56
Maintainers
Readme
@ngsi/store
ng + signal — Angular-native, signal-first state management.
Combines the best of NgRx and NGXS: action classes with decorators, a single state tree, and first-class Angular Signals. Zero RxJS in your components.
Why @ngsi/store?
| Feature | NgRx | NGXS | @ngsi/store |
| ----------------------------- | ----------- | ---- | --------------- |
| Action classes | Factory fns | ✅ | ✅ |
| Decorator-based handlers | ❌ | ✅ | ✅ |
| Native Signal<T> output | ❌ | ❌ | ✅ |
| No toSignal() bridge needed | ❌ | ❌ | ✅ |
| computed() selectors | ❌ | ❌ | ✅ |
| Effects stay in RxJS | ✅ | ✅ | ✅ |
| Redux DevTools | ✅ | ✅ | ✅ |
Installation
npm install @ngsi/storePeer dependencies: @angular/core >= 17, rxjs >= 7
Quick Start
1. Define actions
export class LoginSuccess {
static readonly type = '[Auth] Login Success';
constructor(
public user: CurrentUser,
public accessToken: string
) {}
}
export class Logout {
static readonly type = '[Auth] Logout';
}2. Define state
import { SignalState, On, Effect } from '@ngsi/store';
interface AuthStateModel {
isAuthenticated: boolean;
currentUser: CurrentUser | null;
}
@SignalState({
name: 'auth',
defaults: { isAuthenticated: false, currentUser: null }
})
export class AuthState {
@On(LoginSuccess)
loginSuccess(state: AuthStateModel, action: LoginSuccess): Partial<AuthStateModel> {
return { isAuthenticated: true, currentUser: action.user };
}
@On(Logout)
logout(): Partial<AuthStateModel> {
return { isAuthenticated: false, currentUser: null };
}
@Effect(LoginSuccess)
onLogin(action: LoginSuccess) {
// Return an Observable/Promise to dispatch follow-up actions
return this.http.post('/audit/login', { user: action.user });
}
}3. Define selectors
import { createSelector } from '@ngsi/store';
// Selectors are factory functions — call them in injection context
export const selectCurrentUser = createSelector(AuthState, (s) => s.currentUser);
export const selectIsAuthenticated = createSelector(AuthState, (s) => s.isAuthenticated);
// Compose with computed()
export function selectDisplayName(): Signal<string> {
const user = selectCurrentUser();
return computed(() => user()?.name ?? 'Guest');
}4. Register in app.config.ts
import { provideSignalStore, withDevtools } from '@ngsi/store';
export const appConfig: ApplicationConfig = {
providers: [provideSignalStore([AuthState], withDevtools({ disabled: environment.production }))]
};5. Use in components — pure signals, no RxJS
import { selectCurrentUser, selectIsAuthenticated } from './auth.selectors';
import { injectDispatch } from '@ngsi/store';
@Component({ ... })
export class AppComponent {
// Signals — no toSignal(), no async pipe, no subscriptions
protected readonly currentUser = selectCurrentUser();
protected readonly isAuthenticated = selectIsAuthenticated();
protected readonly displayName = selectDisplayName();
private readonly dispatch = injectDispatch();
login() {
this.dispatch(new LoginSuccess(user, token));
}
}API Reference
Decorators
| Decorator | Target | Description |
| ---------------------------------- | ------ | --------------------------------------------------------------- |
| @SignalState({ name, defaults }) | Class | Marks a class as a state slice |
| @On(...ActionClasses) | Method | Pure state handler — return Partial<State> |
| @Effect(...ActionClasses) | Method | Side-effect handler — return Observable, Promise, or void |
DI
| Function | Description |
| ------------------------------------------- | ------------------------------------------ |
| provideSignalStore(states[], ...features) | Register store + states in app.config.ts |
| injectState(StateClass) | Inject raw Signal<StateModel> |
| injectDispatch() | Inject (action: object) => void |
| createSelector(StateClass, projector) | Create a memoized Signal<R> factory |
Features
| Function | Description |
| ----------------------- | -------------------------------------------- |
| withDevtools(config?) | Redux DevTools browser extension integration |
Testing
import { provideSignalStoreTesting, dispatchInTest } from '@ngsi/store/testing';
await TestBed.configureTestingModule({
imports: [MyComponent],
providers: [provideSignalStoreTesting(AuthState)]
}).compileComponents();
const store = TestBed.inject(SignalStore);
dispatchInTest(store, new LoginSuccess(mockUser, 'token'));