ng-zustand
v1.0.2
Published
The [Zustand](https://github.com/pmndrs/zustand) adapter for angular. Based on https://github.com/JoaoPauloLousada/ngx-zustand adding new method and add support to some elements.
Downloads
6
Readme
NgZustand
The Zustand adapter for angular. Based on https://github.com/JoaoPauloLousada/ngx-zustand adding new method and add support to some elements.
Thx to @JoaoPauloLousada to start the project.
Installation
with npm:
npm install ng-zustand zustand
with yarn:
yarn add ng-zustand zustand
First create a store
Create a service that extends ZustandBaseService.
interface CounterState {
counter: number;
increment: () => void;
decrement: () => void;
}
@Injectable({
providedIn: 'root',
})
export class CounterService extends ZustandBaseService<CounterState> {
initStore() {
return (set) => ({
counter: 0,
increment: () => set((state) => ({ counter: state.counter + 1 })),
decrement: () => set((state) => ({ counter: state.counter - 1 })),
});
}
}
Use the service in your components
@Component({
selector: 'app-counter-page',
standalone: true,
imports: [CommonModule],
template: `
<div *ngIf="store$ | async as store">
<div>
count: {{ store.counter }}
<div>
<div><button (click)="store.increment()">+</button></div>
<div><button (click)="store.decrement()">-</button></div>
</div>
</div>
</div>
`,
styles: [],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CounterPageComponent {
private counterService = inject(CounterService);
store$ = this.counterService.useStore();
}
Recipes
Fetching Everything
store$ = this.store.useStore();
Selecting multiple state slices
foo$ = this.store.useStore((state) => state.foo);
bar$ = this.store.useStore((state) => state.bar);
fooAndBar$ = this.store.useStore((state) => ({
foo: state.foo,
bar: state.bar,
}));
Async actions
Just call set when you're ready, zustand doesn't care if your actions are async or not.
export class TodosStore extends ZustandBaseService<TodosState> {
initStore() {
return (set) => ({
todos: [],
loadTodos: () => {
this.http.get<Todo[]>().subscribe((todos) => set({ todos }));
},
});
}
}
Read from state in actions
set allows fn-updates set(state => result), but you still have access to state outside of it through get.
export class TodosStore extends ZustandBaseService<TodosState> {
initStore() {
return (set, get) => ({
todos: [],
action: () => {
const todos = get().todos;
},
});
}
}
Redux devtools middleware
You can override createStore function in order to include the middlewares you need.
import { devtools } from 'zustand/middleware';
@Injectable({
providedIn: 'root',
})
export class CounterService extends ZustandBaseService<CounterState> {
initStore() {
return devtools<CounterState>((set) => ({
counter: 0,
increment: () => set((state) => ({ counter: state.counter + 1 })),
decrement: () => set((state) => ({ counter: state.counter - 1 })),
}));
}
}
Persist middleware
import { createJSONStorage, persist } from 'zustand/middleware';
@Injectable({
providedIn: 'root',
})
export class CounterService extends ZustandBaseService<CounterState> {
initStore(): StateCreator<CounterState> {
return (set) => ({
counter: 0,
increment: () => set((state) => ({ counter: state.counter + 1 })),
decrement: () => set((state) => ({ counter: state.counter - 1 })),
});
}
override createStore() {
return createStore(
persist<CounterState>(this.initStore(), {
name: 'counterStore',
storage: createJSONStorage(() => sessionStorage),
})
);
}
}
Middleware
You can functionally compose your store any way you like. Please check typescript guide to a better explanation of how to type middlewares.
// Log every time state is changed
const logMiddleware = (config) => (set, get, api) =>
config(
(...args) => {
console.log(' applying', args);
set(...args);
console.log(' new state', get());
},
get,
api
);
export class CounterService extends ZustandBaseService<CounterState> {
initStore() {
return logMiddleware((set) => ({
counter: 0,
increment: () => set((state) => ({ counter: state.counter + 1 })),
decrement: () => set((state) => ({ counter: state.counter - 1 })),
}));
}
}