Skip to content

DI во ViewModel

Задача

Использовать инфраструктурные services внутри view model, не хардкодя их в UI или глобальные модули.

Решение

Инжектить services через слой DI-интеграции и использовать их внутри actions view model.

Код

ts
import { declareViewModel, readonlyAtom } from '@nrgyjs/core';
import { withInjections } from '@nrgyjs/ditox';
import { token } from 'ditox';

const USER_API = token<{ loadName(): Promise<string> }>();

export const UserNameViewModel = declareViewModel()
  .extend(withInjections({ userApi: USER_API }))
  .apply(({ scope, deps }) => {
    const loading = scope.atom(false);
    const name = scope.atom<string | null>(null);

    return {
      state: {
        loading: readonlyAtom(loading),
        name: readonlyAtom(name),
      },
      load: async () => {
        loading.set(true);

        try {
          name.set(await deps.userApi.loadName());
        } finally {
          loading.set(false);
        }
      },
    };
  });
tsx
import { useAtoms, withViewModel } from '@nrgyjs/react';

const UserNameCard = withViewModel(UserNameViewModel)(({ viewModel }) => {
  const { loading, name } = useAtoms(viewModel.state);

  return (
    <section>
      <button disabled={loading} onClick={() => void viewModel.load()}>
        Load user
      </button>
      <div>{name ?? 'Unknown user'}</div>
    </section>
  );
});

На что обратить внимание

  • инжектить нужно инфраструктурные зависимости, а не feature params
  • DI wiring не должно жить внутри UI-компонентов
  • view model должна отдавать наружу только view-facing state и actions

Частые ошибки

  • использовать DI для значений, которые должны быть явными feature inputs
  • импортировать инфраструктурные singleton-объекты напрямую во view
  • протаскивать container details в UI-facing contracts