1.0.0-beta.1 • Published 1 year ago

ngx-zustand v1.0.0-beta.1

Weekly downloads
-
License
-
Repository
github
Last release
1 year ago

NgxZustand

The Zustand adapter for angular.

Installation

with npm:

npm install ngx-zustand zustand

with yarn:

yarn add ngx-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 })),
    }));
  }
}
1.0.0-beta.1

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