ajwahjs

Framework agnostic state management tool without ceremonies and boilerplates.

Usage no npm install needed!

<script type="module">
  import ajwahjs from 'https://cdn.skypack.dev/ajwahjs';
</script>

README

ajwahjs

Framework agnostic state management tool without ceremonies and boilerplates.

Reactive state management library. Manage your application's states, effects, and actions easy way. Make apps more scalable with a unidirectional data-flow.

Every StateController has the following features:

  • Dispatching actions
  • Filtering actions
  • Adding effects
  • Communications among Controllers[Although they are independents]

CounterState

interface CounterState {
  count: number;
  loading: bool;
}

class CounterStateCtrl extends StateController<CounterState> {
  constructor() {
    super({ count: 0, loading: false });
  }
  onInit() {}

  inc() {
    this.emit({ count: this.state.count++ });
  }

  dec() {
    this.emit({ count: this.state.count-- });
  }

  async asyncInc() {
    this.emit({ loading: true });
    await delay(1000);
    this.emit({ count: this.state.count++, loading: false });
  }

  asyncIncBy = effect<number>((num$) =>
    num$.pipe(
      tap((_) => this.emit({ loading: true })),
      delay(1000),
      tap((by) => this.emit({ count: this.state.count + by, loading: false }))
    )
  );
}

Consuming State in

Vanilla js

const csCtrl = Get(CounterStateCtrl);
csCtrl.stream$.subscrie(console.log);
csCtrl.inc();
csCtrl.dec();
csCtrl.asyncInc();
csCtrl.asyncIncBy(5);

React

const CounterComponent = () => {
  const csCtrl = Get(CounterStateCtrl);

  const data = useStream(csCtrl.stream$, csCtrl.state);

  return (
    <p>
      <button className="btn" onClick={() => csCtrl.inc()}>
        +
      </button>
      <button className="btn" onClick={() => csCtrl.dec()}>
        -
      </button>
      <button className="btn" onClick={() => csCtrl.asyncInc()}>
        async(+)
      </button>
      {data.loading ? 'loading...' : data.count}
    </p>
  );
};

Angular

@Component({
  selector: 'app-counter',
  template: `
    <p>
      <button class="btn" (click)="csCtrl.inc()">+</button>
      <button class="btn" (click)="csCtrl.dec()">-</button>
      <button class="btn" (click)="csCtrl.asyncIn())">async(+)</button>
      <span *ngIf="csCtrl.stream$ | async as state"
        >{{ state.loading ? 'loading...' : state.count }}
      </span>
    </p>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CounterComponent {
  constructor(public csCtrl: CounterStateCtrl) {}
}

Vue

<template>
  <p>
    <button class="btn" @click="inc()">+</button>
    <button class="btn" @click="dec()">-</button>
    <button class="btn" @click="asyncInc()">async(+)</button>
    {{ state.loading?'loading...':state.count }}
  </p>
</template>

export default {
  name: "Counter",
  components: {},
  setup() {
    const csCtrl = Get(CounterStateCtrl);

    const state = useStream(csCtrl.stream$, csCtrl.state);

    function inc() {
      csCtrl.inc();
    }
    function dec() {
      csCtrl.dec();
    }
    function asyncInc() {
      csCtrl.asyncInc();
    }

    return { inc, dec, asyncInc, state };
  },
};

Effects

onInit() {
    this.effectOnAction(
      this.action$.isA(AsyncInc).pipe(
        tap((_) => this.emit({ loading: true })),
        delay(1000),
        map((action) => ({ count: this.state.count + action.data, loading: false  }))
    ));
}

asyncIncBy = effect<number>((num$) =>
  num$.pipe(
    tap((_) => this.emit({ loading: true })),
    delay(1000),
    tap((by) => this.emit({ count: this.state.count + by, loading: false }))
  )
);

Combining States


 get todos$() {
    return combineLatest([
      this.stream$,
      this.remoteStream<SearchCategory>(SearchCategoryStateCtrl)
    ]).pipe(
      map(([todos, searchCategory]) => {
        switch (searchCategory) {
          case SearchCategory.active:
            return todos.filter(todo => !todo.completed);
          case SearchCategory.completed:
            return todos.filter(todo => todo.completed);
          default:
            return todos;
        }
      })
    );
  }

counter : Angular Demo | React Demo | Vue Demo

todos : Angular Demo | React Demo | Vue Demo