Skip to content

DI in a ViewModel

Task

Use infrastructure services inside a view model without hardcoding them into UI or global modules.

Solution

Inject services through the DI integration layer and consume them inside view model actions.

Code

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>
  );
});

What to Watch Out For

  • inject infrastructure dependencies, not feature params
  • keep DI wiring outside UI components
  • expose only view-facing state and actions from the view model

Common Mistakes

  • using DI for values that should be explicit feature inputs
  • importing infrastructure singletons directly into the view
  • leaking container details into UI-facing contracts