In Angular, we can inject services into components so that components can share state and communicate with each other. In React, we can use context to provide services to components too. Here is some sample code.

// counterService.tsx
import { createContext, useContext, useState } from "react";

export interface CounterService {
  count: number;
  increment: () => void;
  decrement: () => void;
}

const CounterServiceContext = createContext({} as CounterService);

export const useCounterService = () => {
  return useContext(CounterServiceContext);
};

export const CounterServiceProvider = ({
  value,
  children,
}: {
  value?: CounterService;
  children: React.ReactElement;
}) => {
  const [count, setCount] = useState(0);

  const increment = () => setCount((prev) => prev + 1);
  const decrement = () => setCount((prev) => prev - 1);

  const service = value || {
    count,
    increment,
    decrement,
  };

  return (
    <CounterServiceContext.Provider value={service}>
      {children}
    </CounterServiceContext.Provider>
  );
};

Well, it is not that complicated. But it need to know how to use context. This can be even more simplified with the help of a helper function.

// counterService-simplified.tsx
import { useState } from "react";
import { provideService } from "./provideService";

export const {
  ServiceProvider: CounterServiceProvider,
  useService: useCounterService,
} = provideService(() => {
  const [count, setCount] = useState(0);
  return {
    increment() {
      setCount(count + 1);
    },
    decrement() {
      setCount(count - 1);
    },
    count,
  };
});

Now you just need to pass a factory function to the helper function provideService. The following is the implementation of the helper function.

// provideService.tsx
import React, { createContext, useContext } from "react";

export function provideService<T>(serviceFactory: () => T) {
  const context = createContext<T>({} as T);
  return {
    useService() {
      return useContext(context);
    },
    ServiceProvider({
      value,
      children,
    }: {
      value?: T;
      children: React.ReactElement;
    }) {
      const service = value || serviceFactory();
      return <context.Provider value={service}>{children}</context.Provider>;
    },
  };
}

To use the service, you can use the following code.

import {
  CounterServiceProvider,
  useCounterService,
} from "./counterService-simplified";

function CounterControls() {
  const countService = useCounterService();
  return (
    <p>
      <button onClick={countService.increment}> Increment </button>
      <button onClick={countService.decrement}> Decrement </button>
    </p>
  );
}

function CounterDisplay() {
  const countService = useCounterService();
  return <p>Count: {countService.count}</p>;
}

export function Counter() {
  return (
    <CounterServiceProvider>
      <div>
        <h2>Counter</h2>
        <CounterDisplay />
        <CounterControls />
      </div>
    </CounterServiceProvider>
  );
}

The service also can be different from the one created the service factory. The following code override the original service.

export function Counter2() {
  const [count, setCount] = useState(10);
  const service = {
    increment() {
      setCount(count + 2);
    },
    decrement() {
      setCount(count - 2);
    },
    count,
  };
  return (
    <CounterServiceProvider value={service}>
      <div>
        <h2>Counter 2</h2>
        <CounterDisplay />
        <CounterControls />
      </div>
    </CounterServiceProvider>
  );
}

The full source code is hosted here