Universal Model

Unified state management for Angular, React, Svelte and Vue.
Universal model provides unified state management: store, states, actions, selectors and services are the same for every UI framework, only view parts differ.

1. Create initial state

export interface Todo {
  id: number;
  name: string;
  isDone: boolean;
}

export default {
  todos: [] as Todo[],
  shouldShowOnlyUnDoneTodos: false,
  isFetchingTodos: false,
  hasTodosFetchFailure: false
};

2. Create state selectors

const createTodoListStateSelectors = <T extends State>() => ({
  shownTodos: (state: T) =>
    state.todosState.todos.filter(
      (todo: Todo) =>
        (state.todosState.shouldShowOnlyUnDoneTodos && !todo.isDone) ||
        !state.todosState.shouldShowOnlyUnDoneTodos
    ),
  todoCount: (state: T) => state.todosState.todos.length,
  unDoneTodoCount: (state: T) => state.todosState.todos.filter((todo: Todo) => !todo.isDone).length
});

3. Create a store (using initial states and selectors)

const initialState = {
  todosState: createSubState(initialTodoListState)
};

export type State = typeof initialState;

const todoListStateSelectors = createTodoListStateSelectors<State>();

const selectors = combineSelectors<State, typeof todoListStateSelectors>(
  todoListStateSelectors
);

export default createStore<State, typeof selectors>(initialState, selectors);

4. Create some actions

export async function fetchTodos(): Promise<void> {
  const { todosState } = store.getState();

  todosState.isFetchingTodos = true;
  todosState.hasTodosFetchFailure = false;

  try {
    todosState.todos = await todoService.tryFetchTodos();
  } catch (error) {
    todosState.hasTodosFetchFailure = true;
  }

  todosState.isFetchingTodos = false;
}


export function removeTodo(todoToRemove: Todo): void {
  const { todosState } = store.getState();
  todosState.todos = todosState.todos.filter((todo: Todo) => todo !== todoToRemove);
}


export function toggleIsDoneTodo(todo: Todo): void {
  todo.isDone = !todo.isDone;
}

5. Create a view (Angular)

@Component({
  selector: 'app-todo-list-view',
  template: `
    <div>
      <div *ngIf="todosState.isFetchingTodos">Fetching todos...</div>
      <div *ngIf="todosState.hasTodosFetchFailure; else todoList">Failed to fetch todos</div>
      <ng-template #todoList>
        <ul>
          <li *ngFor="let todo of shownTodos">
            <input id="todo.name" type="checkbox" [checked]="todo.isDone" (click)="toggleIsDoneTodo(todo)" />
            <label for="todo.name">{{ todo.name }}</label>
            <button (click)="removeTodo(todo)">Remove</button>
          </li>
        </ul>
      </ng-template>
    </div>
  `,
  styleUrls: []
})
export class TodoListComponent implements OnInit {
  todosState: typeof initialTodosState;
  shownTodos: Todo[];
  toggleIsDoneTodo = toggleIsDoneTodo;
  removeTodo = removeTodo;

  constructor() {
    const [{ todosState }, { shownTodos }] = store.getStateAndSelectors();
    store.useStateAndSelectors(this, { todosState }, { shownTodos });
  }

  ngOnInit(): void {
    fetchTodos();
  }
}