@norabytes/reactjs-ioc v1.2.4
NoraBytes © 2024
ReactJS Inversion of Control (Compatible with NextJS!)
Installation
npm install @norabytes/reactjs-ioc
or
yarn add @norabytes/reactjs-ioc
Usage
Steps to correctly enable the Reflect API.
- Install the @abraham/reflection library or the reflect-metadata library.
- Provide the installed Reflect API
polyfillby either doing:- In your
.envfile add:NODE_OPTIONS=--require @abraham/reflectionorNODE_OPTIONS=--require reflect-metadata - Manually import it with
import '@abraham/reflection'orimport 'reflect-metadata'into your appentrypointfile. - Use the
polyfillsprovider of yourtranspilerof choice.
- In your
Steps required only if using TS.
- Make sure that your
TSversion is at least4.7.0 - In your
tsconfig.jsoninside thecompilerOptionsobject:"experimentalDecorators": true"emitDecoratorMetadata": true"moduleResolution": "NodeNext"
Example
Global Module
- Create an
ApiServicewhich will be provided globally (Singleton) from theAppModule.
// ./services/api/api.service.ts
import { Injectable } from '@norabytes/reactjs-ioc';
@Injectable()
export class ApiService {
async fetchData(endpoint: string, data: any): Promise<Response> {
// Fetch data logic
}
}// ./services/api/api.module.ts
import { ProviderModule } from '@norabytes/reactjs-ioc';
import { ApiService } from './api.service';
export const ApiServiceModule = new ProviderModule({
providers: [ApiService],
});- Create an
UserServicewhich will have access to theApiServicebyconstructorinjection.
// ./services/user/user.service.ts
import { Injectable } from '@norabytes/reactjs-ioc';
@Injectable()
export class UserService {
constructor(private readonly apiService: ApiService) {}
async createNewUser(userData: UserData): Promise<bool> {
// You can access the `ApiService` everywhere inside your `UserService` instance.
const response = await this.apiService.fetchData('/api/v1/create-user', userData);
}
}// ./services/user/user.module.ts
import { ProviderModule } from '@norabytes/reactjs-ioc';
import { UserService } from './user.service';
export const UserServiceModule = new ProviderModule({
providers: [UserService],
});- Create an
app.module.tsfile:
// ./app.module.ts
import { ProviderModule } from '@norabytes/reactjs-ioc';
// Modules
import { ApiServiceModule } from './api';
import { UserServiceModule } from './user';
export const AppModule = new ProviderModule({
// By importing these modules, their `providers` will be automatically resolved by the `injector` container.
imports: [ApiServiceModule, UserServiceModule],
});You can now inject both the ApiService and the UserService in your ReactJS functional components by doing so:
The below is an example of an
HoComponentwhich wraps your entire ReactJS app, this should be used to providesingletonsglobally down to your entire app.
// ./app.tsx
import React from 'react';
import { Injector } from '@norabytes/reactjs-ioc';
import { InjectorProvider } from '@norabytes/reactjs-ioc/r';
import { AppModule } from './app.module';
import { RootLayout } from './root.layout';
export function App({ children }: { React.ReactElement }) {
return (
<InjectorProvider injectInto="root" module={AppModule}>
<RootLayout>{children}</RootLayout>
</InjectorProvider>
);
}// ./pages/home.tsx
// The `Homepage` components is rendered by the `RootLayout` somewhere down the tree.
import { useEffect } from 'react';
import { useInject } from '@norabytes/reactjs-ioc/r';
import { ApiService } from '../services/api';
export function Homepage() {
const apiService = useInject(ApiService);
useEffect(() => {
apiService.fetchData('url', data);
}, []);
}- Limit which
providerscan beexportedfrom aProviderModule.
import { Injector, ProviderModule } from '@norabytes/reactjs-ioc';
import { UserService } from './user.service';
import { UserDataService } from './user-data.service';
const UserServiceModule = new ProviderModule({
providers: [UserService, UserDataService],
// By default all the listed `providers` are exported.
// If you want to control which providers should be exported outside this `ProviderModule`,
// you can list them here.
//
// In this example, when another `ProviderModule` imports the `UserServiceModule`,
// it'll only have access to the `UserService`.
exports: [UserService],
});
console.log(UserServiceModule.getProviders());
// => [UserService, UserDataService]
const GlobalModule = new ProviderModule({
imports: [UserServiceModule],
});
console.log(GlobalModule.getProviders());
// => [UserService]
const userContainer = Injector.createTransientInjector({ module: UserServiceModule });
console.log(userContainer.get(UserService));
// => OK
console.log(userContainer.get(UserDataService));
// => OK
const globalContainer = Injector.createTransientInjector({ module: GlobalModule });
console.log(globalContainer.get(UserService));
// => OK
console.log(globalContainer.get(UserDataService));
// => Error: No provider for `UserDataService`.- Inject
on-the-fly.
import { Injectable, Inject } from '@norabytes/reactjs-ioc';
import { UserDataService } from './user-data.service';
// N.B: You'll still have to provide a `ProviderModule` to an `injector`.
@Injectable()
export class UserService {
constructor(@Inject(UserDataService) private readonly userDataService: UserDataService) {}
private applyDataToUser(data: any): void {
this.userDataService.applyData(data);
}
}NextJS
Client Components
If you are using the new
appfolder introduced withNextJS v13, then you should be careful to add the'use client'directive at the top of the components which are going to use theuseInjecthook and theInjectorProviderHoComponent.
Server Components
FAQs:
- How to have a
rootcontainer for my entire app without using the'use client'directive?- Instead of using the HoC
InjectorProvider, you can use theInjectorAPI to create ascopedcontainer, then use that container to inject directly into your components.eg:
// ./app.tsx import React from 'react'; import { Injector } from '@norabytes/reactjs-ioc'; import { AppModule } from './app.module'; import { RootLayout } from './root.layout'; export const MY_APP_CONTAINER_KEY = 'APP_CONTAINER_KEY'; Injector.createScopedInjector({ key: MY_APP_CONTAINER_KEY, module: AppModule, // If you have already injected some modules into the `root` container and you want to be able to also resolve // those dependencies when using this `scoped` container, you must also add the below line. fromRootInjector: true }); export function App({ children }: { React.ReactElement }) { return <RootLayout>{children}</RootLayout>; }import { Injector } from '@norabytes/reactjs-ioc'; import React from 'react'; import { MY_APP_CONTAINER_KEY } from '...'; import { MyService } from '...'; export async function MyServerComponent({ children }: { React.ReactElement }) { // Now you have access to your service/dependency. // // N.B: While this would work, it is not the best approach, you should strive to use the `useInject` // hook as it is optimized especially to be used with ReactJS functional components. // Therefore the correct way would be to use the `'use client'` directive at the top of any component // which must have some dependencies injected into it. const myService = Injector.getScoped(MY_APP_CONTAINER_KEY, MyService); return <h1>Hello NoraBytes!</h1>; }
- Instead of using the HoC
Unit Tests
Below some examples of how to mock your dependencies in your Unit Tests
// ./my-component/tests/mocks/my-component.service.mock.ts
import { Injectable } from '@norabytes/reactjs-ioc';
import { MyComponentService, AnotherService } from '...';
@Injectable()
export class MyComponentServiceMock extends MyComponentService {
constructor(private override readonly anotherService: AnotherService) {}
override realMethod(): void {
console.log('The `realMethod` has been mocked!');
}
}// ./my-component/tests/mocks/my-component.module.mock.ts
import { ProviderModule } from '@norabytes/reactjs-ioc';
import { MyComponentService, MyComponentServiceMock, AnotherService } from '...';
export const MyComponentModuleMock = new ProviderModule({
providers: [
{ provide: MyComponentService, useClass: MyComponentServiceMock },
AnotherService
];
});// ./my-component/tests/mocks/my-component.mock.ts
import { InjectorProvider } from '@norabytes/reactjs-ioc/r';
import { MyComponent, MyComponentModuleMock } from '...';
export function MyComponentMock(props: PropsType) {
return (
<InjectorProvider module={MyComponentModuleMock}>
<MyComponent />
</InjectorProvider>
);
}Now you can use the MyComponentMock in your Unit Tests.
Injector API
Check also the API of the injection-js library.
As this library is natively built with TypeScript, we encourage you to just check the IInjectorFactory interface which can be found
at node_modules/@norabytes/reactjs-ioc/dist/src/types/injector/injector-factory.d.ts
11 months ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago