@adimm/x-injection-reactjs 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +174 -477
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -14,6 +14,8 @@
14
14
 
15
15
  **Stop wrestling with React Context and prop drilling. Build scalable React apps with clean, testable business logic separated from UI.**
16
16
 
17
+ > **TL;DR** — Mark classes with `@Injectable()`, declare a `ProviderModule.blueprint()`, wrap your component with `provideModuleToComponent(MyModuleBp, () => { ... })`, then call `useInject(MyService)` inside. Dependencies are resolved automatically — no providers, no prop drilling, no manual wiring.
18
+
17
19
  ## Table of Contents
18
20
 
19
21
  - [Table of Contents](#table-of-contents)
@@ -22,28 +24,21 @@
22
24
  - [2. Prop Drilling](#2-prop-drilling)
23
25
  - [3. Manual Dependency Wiring](#3-manual-dependency-wiring)
24
26
  - [4. Business Logic Mixed with UI](#4-business-logic-mixed-with-ui)
25
- - [How xInjection Solves This](#how-xinjection-solves-this)
26
27
  - [Installation](#installation)
27
28
  - [Quick Start](#quick-start)
28
29
  - [How It Works](#how-it-works)
29
30
  - [1. Services: Your Business Logic](#1-services-your-business-logic)
30
31
  - [2. Modules: Organizing Dependencies](#2-modules-organizing-dependencies)
31
32
  - [3. Injecting Services into Components](#3-injecting-services-into-components)
32
- - [Why Use the HoC Approach?](#why-use-the-hoc-approach)
33
- - [1. Component Lifecycle Integration](#1-component-lifecycle-integration)
34
- - [2. Isolated Dependency Trees](#2-isolated-dependency-trees)
35
- - [3. Composition and Reusability](#3-composition-and-reusability)
36
- - [4. Works with Standard React Patterns](#4-works-with-standard-react-patterns)
37
33
  - [The Power of Component-Scoped Modules](#the-power-of-component-scoped-modules)
38
34
  - [What Are Component-Scoped Modules?](#what-are-component-scoped-modules)
39
35
  - [Pattern 1: Multiple Independent Instances](#pattern-1-multiple-independent-instances)
40
36
  - [Pattern 2: Parent-Child Dependency Control](#pattern-2-parent-child-dependency-control)
41
- - [Pattern 3: Global + Component-Scoped Mixing](#pattern-3-global--component-scoped-mixing)
42
- - [All Ways to Use This Library](#all-ways-to-use-this-library)
43
- - [Basic Service Injection](#basic-service-injection)
44
- - [Injecting Multiple Services](#injecting-multiple-services)
37
+ - [Why Use the HoC Approach?](#why-use-the-hoc-approach)
38
+ - [1. Lifecycle-Bound Isolated Containers](#1-lifecycle-bound-isolated-containers)
39
+ - [2. Composition and Reusability](#2-composition-and-reusability)
40
+ - [Hierarchical Dependency Injection](#hierarchical-dependency-injection)
45
41
  - [Creating Custom Hooks with Dependencies](#creating-custom-hooks-with-dependencies)
46
- - [Global vs Component-Scoped Services](#global-vs-component-scoped-services)
47
42
  - [Parent Components Controlling Child Dependencies](#parent-components-controlling-child-dependencies)
48
43
  - [Module Imports and Exports](#module-imports-and-exports)
49
44
  - [Real-World Examples](#real-world-examples)
@@ -52,27 +47,15 @@
52
47
  - [Testing Your Code](#testing-your-code)
53
48
  - [Mocking an Entire Module](#mocking-an-entire-module)
54
49
  - [Mocking on-the-fly](#mocking-on-the-fly)
55
- - [API Reference](#api-reference)
56
- - [Core Functions](#core-functions)
57
- - [`provideModuleToComponent(module, component)`](#providemoduletocomponentmodule-component)
58
- - [`useInject(ServiceClass, options?)`](#useinjectserviceclass-options)
59
- - [`useInjectMany(...services)`](#useinjectmanyservices)
60
- - [`hookFactory({ use, inject })`](#hookfactory-use-inject-)
61
- - [`@Injectable()`](#injectable)
62
- - [`ProviderModule.blueprint(definition)`](#providermoduleblueprintdefinition)
63
- - [`ProviderModule.create(definition)`](#providermodulecreatedefinition)
64
50
  - [FAQ](#faq)
65
51
  - [How do I add global services?](#how-do-i-add-global-services)
66
52
  - [When should I use global modules vs component-scoped modules?](#when-should-i-use-global-modules-vs-component-scoped-modules)
67
53
  - [Can I use this with Redux/MobX/Zustand?](#can-i-use-this-with-reduxmobxzustand)
68
54
  - [How does this compare to React Context?](#how-does-this-compare-to-react-context)
69
- - [Do I need to understand Dependency Injection to use this?](#do-i-need-to-understand-dependency-injection-to-use-this)
70
55
  - [If I want Angular patterns, why not just use Angular?](#if-i-want-angular-patterns-why-not-just-use-angular)
71
56
  - [Can I migrate gradually from an existing React app?](#can-i-migrate-gradually-from-an-existing-react-app)
72
57
  - [When do I actually need `provideModuleToComponent`?](#when-do-i-actually-need-providemoduletocomponent)
73
58
  - [What's the performance impact?](#whats-the-performance-impact)
74
- - [Is this production-ready?](#is-this-production-ready)
75
- - [Is "provider hell" really that bad?](#is-provider-hell-really-that-bad)
76
59
  - [Why use classes for services instead of custom hooks?](#why-use-classes-for-services-instead-of-custom-hooks)
77
60
  - [Links](#links)
78
61
  - [Contributing](#contributing)
@@ -156,20 +139,7 @@ function UserDashboard() {
156
139
  }
157
140
  ```
158
141
 
159
- ## How xInjection Solves This
160
-
161
- xInjection brings **Inversion of Control (IoC)** and **Dependency Injection (DI)** to React—concepts from Angular and NestJS that solve these exact problems. Don't worry if those terms sound fancy; the idea is simple:
162
-
163
- **Instead of components creating and managing their own dependencies, they just ask for what they need, and xInjection provides it.**
164
-
165
- - **No Provider Hell** - One module replaces nested providers
166
- - **No Prop Drilling** - Services are injected directly where needed
167
- - **Automatic Dependency Resolution** - Dependencies are wired automatically
168
- - **Clean Separation** - Business logic lives in services, UI stays in components
169
- - **Fully Testable** - Mock services easily for testing
170
- - **Type-Safe** - Full TypeScript support
171
-
172
- ---
142
+ xInjection solves all of the above by bringing **Inversion of Control (IoC)** and **Dependency Injection (DI)** to React: instead of components creating and managing their own dependencies, they just ask for what they need and xInjection provides it — automatically, type-safely, and testably.
173
143
 
174
144
  This is the official [ReactJS](https://react.dev/) implementation of [xInjection](https://github.com/AdiMarianMutu/x-injection).
175
145
 
@@ -179,8 +149,7 @@ This is the official [ReactJS](https://react.dev/) implementation of [xInjection
179
149
  npm i @adimm/x-injection-reactjs reflect-metadata
180
150
  ```
181
151
 
182
- [!IMPORTANT]
183
-
152
+ > [!IMPORTANT]
184
153
  > Import `reflect-metadata` at the very top of your app entry point:
185
154
 
186
155
  ```tsx
@@ -216,7 +185,9 @@ Add to your `tsconfig.json`:
216
185
 
217
186
  ## Quick Start
218
187
 
219
- Here's a complete example showing both global and component-scoped services:
188
+ Three files, three concepts: global services declared once, a component-scoped module, and a component that injects both.
189
+
190
+ **Step 1 — Declare global services** in your entry point:
220
191
 
221
192
  ```tsx
222
193
  // main.tsx - Your app entry point
@@ -259,6 +230,8 @@ ProviderModule.blueprint({
259
230
  createRoot(document.getElementById('root')!).render(<App />);
260
231
  ```
261
232
 
233
+ **Step 2 — Create a component-scoped module and inject services:**
234
+
262
235
  ```tsx
263
236
  // UserDashboard.tsx - A component with its own service
264
237
 
@@ -295,6 +268,8 @@ export const UserDashboard = provideModuleToComponent(UserDashboardModuleBp, ()
295
268
  });
296
269
  ```
297
270
 
271
+ **Step 3 — Use the component** — each instance gets its own module:
272
+
298
273
  ```tsx
299
274
  // App.tsx
300
275
 
@@ -310,9 +285,7 @@ export default function App() {
310
285
  }
311
286
  ```
312
287
 
313
- [!TIP]
314
-
315
- > **Key points:**
288
+ > [!TIP] Global vs component-scoped services:
316
289
  >
317
290
  > - Global services (`ApiService`, `AuthService`): Defined in a global blueprint, automatically imported into the built-in `AppModule`
318
291
  > - Component-scoped services (`UserDashboardService`): Fresh instance per `<UserDashboard />`
@@ -421,42 +394,21 @@ const TodoListModuleBp = ProviderModule.blueprint({
421
394
  });
422
395
  ```
423
396
 
424
- [!IMPORTANT]
425
-
397
+ > [!IMPORTANT]
426
398
  > When using `isGlobal: true`, only services listed in the `exports` array become globally available. Non-exported providers remain private to the module.
427
399
 
428
- [!CAUTION]
429
-
430
- > **Global modules cannot be used with `provideModuleToComponent`**. Attempting to provide a global module to a component will throw an `InjectionProviderModuleError`. Global services are accessed directly via `useInject` without the HoC.
400
+ > [!CAUTION] Global modules cannot be used with `provideModuleToComponent`
401
+ > Attempting to provide a global module to a component will throw an `InjectionProviderModuleError`. Global services are accessed directly via `useInject` without the HoC.
431
402
 
432
403
  **`blueprint()` vs `create()`:**
433
404
 
434
- - **`blueprint()`**: Creates a **deferred module** (template) instantiated when needed. Use for all React modules—both global and component-scoped. [Learn more](https://github.com/AdiMarianMutu/x-injection?tab=readme-ov-file#blueprints).
435
- - **`create()`**: Immediately initializes a module. Rarely needed in React.
405
+ - **`blueprint()`**: A deferred module template. Each time it is imported or used with `provideModuleToComponent`, a **new independent instance** is created. Use for the global bootstrap module and for component-scoped modules. [Learn more](https://github.com/AdiMarianMutu/x-injection?tab=readme-ov-file#blueprints).
406
+ - **`create()`**: Immediately instantiates a module. The resulting instance is a **single shared object** — every module that imports it shares the exact same instance. Use when you need a module that is instantiated once and shared across multiple other modules.
436
407
 
437
- **Modules can import other modules to compose functionality:**
408
+ See [Module Imports and Exports](#module-imports-and-exports) for examples of both.
438
409
 
439
- ```tsx
440
- // Shared utilities module
441
- const UtilsModuleBp = ProviderModule.blueprint({
442
- id: 'UtilsModule',
443
- providers: [LoggerService, DateService],
444
- exports: [LoggerService, DateService],
445
- });
446
-
447
- // Feature module imports utilities
448
- const UserDashboardModuleBp = ProviderModule.blueprint({
449
- id: 'UserDashboardModule',
450
- imports: [UtilsModuleBp], // Reuse LoggerService and DateService
451
- providers: [UserProfileService], // Add UserProfileService
452
- });
453
- ```
454
-
455
- See [Module Imports and Exports](#module-imports-and-exports) for more advanced patterns.
456
-
457
- [!CAUTION]
458
-
459
- > **Never import `AppModule`** into other modules. `AppModule` is the built-in global container and importing it will throw an error. Use global blueprints with `isGlobal: true` instead, which are automatically imported into `AppModule`.
410
+ > [!CAUTION] Never import `AppModule` into other modules
411
+ > `AppModule` is the built-in global container and importing it will throw an error. Use global blueprints with `isGlobal: true` instead, which are automatically imported into `AppModule`.
460
412
 
461
413
  ### 3. Injecting Services into Components
462
414
 
@@ -486,63 +438,6 @@ const MyComponent = provideModuleToComponent(MyModuleBp, () => {
486
438
  });
487
439
  ```
488
440
 
489
- ## Why Use the HoC Approach?
490
-
491
- You might wonder: "Why wrap my component with `provideModuleToComponent` instead of just using `useInject` directly everywhere?"
492
-
493
- **Short answer:** You don't always need it! If you only use global services, you can just call `useInject` anywhere. But for **component-scoped modules** (where each component instance needs its own services), you need `provideModuleToComponent`.
494
-
495
- The Higher-Order Component (HoC) pattern provides several key benefits:
496
-
497
- ### 1. Component Lifecycle Integration
498
-
499
- The HoC automatically manages the lifecycle of the module and its services. When the component mounts, the module is created along with its services. When it unmounts, the module is disposed (the `onDispose` hook runs), cleaning up only the services defined in that module.
500
-
501
- **With HoC** The component's module and its own services are created/destroyed with the component. Imported services from other modules (including global services) remain unaffected.
502
-
503
- ### 2. Isolated Dependency Trees
504
-
505
- Each component wrapped with `provideModuleToComponent` gets its own isolated dependency container. This means:
506
-
507
- - Two instances of the same component can have different service instances
508
- - Parent components can control child component dependencies
509
- - Services are scoped to the component tree
510
-
511
- ### 3. Composition and Reusability
512
-
513
- The HoC pattern works seamlessly with React's component composition model:
514
-
515
- ```tsx
516
- // Reusable component with its own dependencies
517
- const TodoList = provideModuleToComponent(TodoListModuleBp, () => {
518
- const todoService = useInject(TodoService);
519
- // ...
520
- });
521
-
522
- // Use it multiple times, each with isolated state
523
- function App() {
524
- return (
525
- <>
526
- <TodoList /> {/* Gets its own TodoService */}
527
- <TodoList /> {/* Gets a different TodoService */}
528
- </>
529
- );
530
- }
531
- ```
532
-
533
- ### 4. Works with Standard React Patterns
534
-
535
- The HoC approach works with `React.memo`, context, hooks, and all other React features:
536
-
537
- ```tsx
538
- // Works with React.memo
539
- const MemoizedComponent = React.memo(
540
- provideModuleToComponent(MyModuleBp, () => {
541
- // ...
542
- })
543
- );
544
- ```
545
-
546
441
  ## The Power of Component-Scoped Modules
547
442
 
548
443
  One of the most powerful features of xInjection is **component-scoped modules**. This is something you can't easily achieve with React Context alone.
@@ -601,7 +496,7 @@ const ParentModuleBp = ProviderModule.blueprint({
601
496
 
602
497
  const ChildModuleBp = ProviderModule.blueprint({
603
498
  id: 'ChildModule',
604
- providers: [ChildService],
499
+ providers: [SharedService, ChildService],
605
500
  });
606
501
 
607
502
  const Child = provideModuleToComponent(ChildModuleBp, () => {
@@ -619,83 +514,126 @@ const Parent = provideModuleToComponent(ParentModuleBp, () => {
619
514
 
620
515
  This enables complex patterns like form components sharing validation services, or composite UI components coordinating state.
621
516
 
622
- ### Pattern 3: Global + Component-Scoped Mixing
517
+ ## Why Use the HoC Approach?
623
518
 
624
- Combine global services (singletons) with component-scoped services:
519
+ You might wonder: "Why wrap my component with `provideModuleToComponent` instead of just using `useInject` directly everywhere?"
625
520
 
626
- ```tsx
627
- // Global: Shared across the entire app
628
- ProviderModule.blueprint({
629
- id: 'AppBootstrapModule',
630
- isGlobal: true,
631
- providers: [ApiService, AuthService],
632
- exports: [ApiService, AuthService], // Only exported services are globally available
633
- });
521
+ **Short answer:** You don't always need it! If you only use global services, you can just call `useInject` anywhere. But for **component-scoped modules** (where each component instance needs its own services), you need `provideModuleToComponent`.
634
522
 
635
- // Component-scoped: Fresh instance per component
636
- const UserDashboardModuleBp = ProviderModule.blueprint({
637
- id: 'UserDashboardModule',
638
- providers: [UserDashboardService], // Gets global ApiService + AuthService
639
- });
523
+ The Higher-Order Component (HoC) pattern provides several key benefits:
640
524
 
641
- const UserDashboard = provideModuleToComponent(UserDashboardModuleBp, () => {
642
- const dashboardService = useInject(UserDashboardService);
643
- // UserDashboardService automatically receives the global ApiService and AuthService
525
+ ### 1. Lifecycle-Bound Isolated Containers
526
+
527
+ Each wrapped component gets its **own** dependency container, created on mount and disposed on unmount. Two instances of `<TodoList />` each get their own `TodoService` — they never share state. When the component unmounts, `onDispose` runs automatically, cleaning up only that component's services. Imported global services remain unaffected.
528
+
529
+ ### 2. Composition and Reusability
530
+
531
+ The HoC pattern works seamlessly with React's component composition model:
532
+
533
+ ```tsx
534
+ // Reusable component with its own dependencies
535
+ const TodoList = provideModuleToComponent(TodoListModuleBp, () => {
536
+ const todoService = useInject(TodoService);
537
+ // ...
644
538
  });
539
+
540
+ // Use it multiple times, each with isolated state
541
+ function App() {
542
+ return (
543
+ <>
544
+ <TodoList /> {/* Gets its own TodoService */}
545
+ <TodoList /> {/* Gets a different TodoService */}
546
+ </>
547
+ );
548
+ }
645
549
  ```
646
550
 
647
- This pattern is powerful: global services (API, auth) are singletons, while component-specific services (UserDashboardService) are created per instance.
551
+ ## Hierarchical Dependency Injection
648
552
 
649
- ## All Ways to Use This Library
553
+ Every component wrapped with `provideModuleToComponent` gets its own module container. When `useInject` is called inside that component, xInjection walks a well-defined lookup chain:
650
554
 
651
- This section covers every way you can use xInjection in your React app.
555
+ 1. **Own module** services declared in the component's own blueprint
556
+ 2. **Imported modules** — exported services from modules listed in `imports`
557
+ 3. **AppModule** — globally available services (from `isGlobal: true` blueprints)
652
558
 
653
- ### Basic Service Injection
559
+ ```
560
+ useInject(SomeService) ← called inside <MyComponent />
561
+
562
+
563
+ ┌─────────────────────────┐
564
+ │ MyComponent's module │ ← providers: [MyService, ...]
565
+ │ (own container) │
566
+ └───────────┬─────────────┘
567
+ │ not found
568
+
569
+ ┌─────────────────────────┐
570
+ │ Imported modules │ ← imports: [SharedModule]
571
+ │ (exported only) │ SharedModule.exports: [SharedService]
572
+ └───────────┬─────────────┘
573
+ │ not found
574
+
575
+ ┌─────────────────────────┐
576
+ │ AppModule │ ← AppBootstrapModule { isGlobal: true }
577
+ │ (global services) │ exports: [ApiService, AuthService, ...]
578
+ └───────────┬─────────────┘
579
+ │ not found
580
+
581
+ throws error
582
+ ```
654
583
 
655
- The most common pattern: create a service, add it to a module, inject it into a component.
584
+ **Component example:**
656
585
 
657
586
  ```tsx
587
+ // ① Global services — live in AppModule, available everywhere
658
588
  @Injectable()
659
- class GreetingService {
660
- getGreeting(name: string) {
661
- return `Hello, ${name}!`;
662
- }
663
- }
664
-
665
- const GreetingModuleBp = ProviderModule.blueprint({
666
- id: 'GreetingModule',
667
- providers: [GreetingService],
668
- });
589
+ class ApiService {}
590
+ @Injectable()
591
+ class AuthService {}
669
592
 
670
- const Greeting = provideModuleToComponent(GreetingModuleBp, ({ name }: { name: string }) => {
671
- const greetingService = useInject(GreetingService);
672
- return <h1>{greetingService.getGreeting(name)}</h1>;
593
+ ProviderModule.blueprint({
594
+ id: 'AppBootstrapModule',
595
+ isGlobal: true,
596
+ providers: [ApiService, AuthService],
597
+ exports: [ApiService, AuthService],
673
598
  });
674
- ```
675
599
 
676
- ### Injecting Multiple Services
600
+ // Shared module — created once, imported into component blueprints
601
+ @Injectable()
602
+ class AnalyticsService {}
677
603
 
678
- Use `useInjectMany` to inject multiple services at once:
604
+ const SharedModule = ProviderModule.create({
605
+ id: 'SharedModule',
606
+ providers: [AnalyticsService],
607
+ exports: [AnalyticsService], // ✅ visible to importers
608
+ });
679
609
 
680
- ```tsx
681
- const MyComponent = provideModuleToComponent(MyModuleBp, () => {
682
- const [userService, apiService, toastService] = useInjectMany(UserService, ApiService, ToastService);
610
+ // ③ Component-scoped service — private to this component
611
+ @Injectable()
612
+ class DashboardService {
613
+ constructor(
614
+ private readonly api: ApiService, // resolved from ③ AppModule
615
+ private readonly analytics: AnalyticsService // resolved from ② SharedModule
616
+ ) {}
617
+ }
683
618
 
684
- // Use all three services...
619
+ const DashboardModuleBp = ProviderModule.blueprint({
620
+ id: 'DashboardModule',
621
+ imports: [SharedModule],
622
+ providers: [DashboardService], // ① own container
685
623
  });
686
- ```
687
624
 
688
- **Optional dependencies:**
625
+ const Dashboard = provideModuleToComponent(DashboardModuleBp, () => {
626
+ const dashboard = useInject(DashboardService); // ✅ ① own module
627
+ const analytics = useInject(AnalyticsService); // ✅ ② SharedModule export
628
+ const auth = useInject(AuthService); // ✅ ③ AppModule (global)
689
629
 
690
- ```tsx
691
- const [requiredService, optionalService] = useInjectMany(RequiredService, {
692
- provider: OptionalService,
693
- isOptional: true,
630
+ // useInject(SomePrivateService) // ❌ not found → error
694
631
  });
695
-
696
- // optionalService will be undefined if not provided
697
632
  ```
698
633
 
634
+ > [!TIP]
635
+ > A service that is not listed in a module's `exports` is completely invisible to any component that imports that module. This is how xInjection enforces encapsulation — only what you explicitly export crosses the module boundary.
636
+
699
637
  ### Creating Custom Hooks with Dependencies
700
638
 
701
639
  The `hookFactory` function lets you create reusable custom hooks that automatically receive injected dependencies:
@@ -716,7 +654,7 @@ const useUserProfile = hookFactory({
716
654
  });
717
655
 
718
656
  // Use it in any component
719
- const UserProfile = provideModuleToComponent(UserModuleBp, ({ userId }: { userId: number }) => {
657
+ const UserProfile = provideModuleToComponent<{ userId: number }>(UserModuleBp, ({ userId }) => {
720
658
  const profile = useUserProfile({ userId });
721
659
  return <div>{profile?.name}</div>;
722
660
  });
@@ -759,78 +697,65 @@ useUserData({ userId: 123 }); // With parameters
759
697
  - **`P`**: Hook parameter type (use `void` if no parameters, or `{ param1: type, ... }` for parameters)
760
698
  - **`D`**: Tuple type matching your `inject` array (e.g., `[ApiService, AuthService]`)
761
699
 
762
- [!TIP]
763
-
764
- > **Why use hookFactory?**
700
+ > [!TIP] Why use hookFactory?
765
701
  >
766
702
  > - Dependencies are automatically injected
767
703
  > - Hooks are reusable across components
768
704
  > - Type-safe with TypeScript
769
705
  > - Easier to test (mock dependencies)
770
706
 
771
- ### Global vs Component-Scoped Services
772
-
773
- **When to use which?**
774
-
775
- - **Global (`blueprint` + `isGlobal: true` + `exports`)**: API clients, auth state, routing, theme, toast notifications
776
-
777
- - Only services in the `exports` array become globally available
778
- - **Cannot use `provideModuleToComponent`** - will throw an error
779
- - Just call `useInject` directly anywhere
780
-
781
- - **Component-scoped (`blueprint`)**: Form state, component-specific business logic, UI state
782
- - MUST use `provideModuleToComponent`
783
- - Each component instance gets its own module
784
-
785
- See [Pattern 1](#pattern-1-multiple-independent-instances) and [Pattern 3](#pattern-3-global--component-scoped-mixing) for detailed examples.
786
-
787
707
  ### Parent Components Controlling Child Dependencies
788
708
 
789
709
  The `inject` prop allows parent components to override child component dependencies. See [Pattern 2](#pattern-2-parent-child-dependency-control) for a basic example and the [Complex Form example](#complex-form-with-shared-state) for a real-world use case.
790
710
 
791
711
  ### Module Imports and Exports
792
712
 
793
- Modules can import other modules to reuse their services:
713
+ Modules can import other modules. The key question is: **should the imported module be shared or duplicated per component?**
714
+
715
+ **Shared module instance → `ProviderModule.create()`:**
716
+
717
+ Use `create()` when a module should exist as one instance and be shared by all blueprints that import it:
794
718
 
795
719
  ```tsx
796
- // Base module with shared services
797
- const CoreModuleBp = ProviderModule.blueprint({
720
+ // Instantiated once all importers share the same instance and the same singletons
721
+ const CoreModule = ProviderModule.create({
798
722
  id: 'CoreModule',
799
- providers: [ApiService, LoggerService],
800
- exports: [ApiService, LoggerService], // Make these available to importers
723
+ providers: [SomeSharedService],
724
+ exports: [SomeSharedService],
801
725
  });
802
726
 
803
- // Feature module imports CoreModule
804
727
  const UserModuleBp = ProviderModule.blueprint({
805
728
  id: 'UserModule',
806
- imports: [CoreModuleBp], // Get ApiService and LoggerService
807
- providers: [UserService], // Add UserService (can use ApiService and LoggerService)
729
+ imports: [CoreModule], // every <UserComponent /> shares the same CoreModule
730
+ providers: [UserService],
808
731
  });
809
732
 
810
- // Another feature module
811
733
  const ProductModuleBp = ProviderModule.blueprint({
812
734
  id: 'ProductModule',
813
- imports: [CoreModuleBp], // Also gets ApiService and LoggerService
735
+ imports: [CoreModule], // same CoreModule instance
814
736
  providers: [ProductService],
815
737
  });
816
738
  ```
817
739
 
818
- **Re-exporting modules:**
740
+ **Per-component isolation → blueprint imports:**
741
+
742
+ Import a blueprint when each component instance should get its own independent copy of those providers:
819
743
 
820
744
  ```tsx
821
- const SharedModuleBp = ProviderModule.blueprint({
822
- id: 'SharedModule',
823
- imports: [CoreModuleBp, UtilsModuleBp],
824
- exports: [
825
- CoreModuleBp, // Re-export CoreModule
826
- UtilsModuleBp, // Re-export UtilsModule
827
- ],
745
+ const UserModuleBp = ProviderModule.blueprint({
746
+ id: 'UserModule',
747
+ imports: [FormValidationModuleBp], // each <UserComponent /> gets its own FormValidationService
748
+ providers: [UserService],
828
749
  });
750
+ ```
829
751
 
830
- // Other modules can import SharedModule to get everything
831
- const FeatureModuleBp = ProviderModule.blueprint({
832
- id: 'FeatureModule',
833
- imports: [SharedModuleBp], // Gets CoreModule + UtilsModule
752
+ **Re-exporting:**
753
+
754
+ ```tsx
755
+ const CoreModule = ProviderModule.create({
756
+ id: 'CoreModule',
757
+ imports: [DatabaseModule, CacheModule],
758
+ exports: [DatabaseModule, CacheModule], // expose both to importers
834
759
  });
835
760
  ```
836
761
 
@@ -993,9 +918,7 @@ class InputService {
993
918
  @Injectable()
994
919
  class FormService {
995
920
  constructor(
996
- @Inject({ provide: InputService, useClass: InputService, scope: InjectionScope.Transient })
997
921
  public readonly nameInput: InputService,
998
- @Inject({ provide: InputService, useClass: InputService, scope: InjectionScope.Transient })
999
922
  public readonly emailInput: InputService
1000
923
  ) {
1001
924
  // Initialize with default values
@@ -1021,9 +944,10 @@ class FormService {
1021
944
  const InputModuleBp = ProviderModule.blueprint({
1022
945
  id: 'InputModule',
1023
946
  providers: [InputService],
947
+ exports: [InputService],
1024
948
  });
1025
949
 
1026
- const Input = provideModuleToComponent(InputModuleBp, ({ label }: { label: string }) => {
950
+ const Input = provideModuleToComponent<{ label: string }>(InputModuleBp, ({ label }) => {
1027
951
  const inputService = useInject(InputService);
1028
952
  const [value, setValue] = useState(inputService.value);
1029
953
 
@@ -1045,7 +969,19 @@ const Input = provideModuleToComponent(InputModuleBp, ({ label }: { label: strin
1045
969
  // 4. Form component - injects its InputService instances into child Input components
1046
970
  const FormModuleBp = ProviderModule.blueprint({
1047
971
  id: 'FormModule',
1048
- providers: [FormService, InputService],
972
+ imports: [
973
+ // Clone InputModuleBp and override its defaultScope to Transient for this specific use.
974
+ // Without Transient, both `nameInput` and `emailInput` in FormService would resolve to
975
+ // the same singleton — they'd share state. Transient ensures each @Inject(InputService)
976
+ // parameter in FormService's constructor gets its own independent instance.
977
+ // This is also a good showcase of blueprint dynamicity: the original InputModuleBp is
978
+ // left untouched, and only this consumer opts into Transient behavior.
979
+ InputModuleBp.clone().updateDefinition({
980
+ ...InputModuleBp.getDefinition(),
981
+ defaultScope: InjectionScope.Transient,
982
+ }),
983
+ ],
984
+ providers: [FormService],
1049
985
  exports: [FormService],
1050
986
  });
1051
987
 
@@ -1137,193 +1073,28 @@ it('should render user data', async () => {
1137
1073
  });
1138
1074
  ```
1139
1075
 
1140
- ## API Reference
1141
-
1142
- ### Core Functions
1143
-
1144
- #### `provideModuleToComponent(module, component)`
1145
-
1146
- Wraps a component to provide it with a module's services.
1147
-
1148
- ```tsx
1149
- const MyComponent = provideModuleToComponent(MyModuleBp, (props) => {
1150
- // Component body
1151
- });
1152
- ```
1153
-
1154
- [!CAUTION]
1155
-
1156
- > The module must NOT have `isGlobal: true`. Global modules are accessed directly via `useInject` without the HoC. Providing a global module will throw an `InjectionProviderModuleError`.
1157
-
1158
- #### `useInject(ServiceClass, options?)`
1159
-
1160
- Injects a single service into a component.
1161
-
1162
- ```tsx
1163
- const service = useInject(MyService);
1164
- const optionalService = useInject(OptionalService, { isOptional: true });
1165
- ```
1166
-
1167
- #### `useInjectMany(...services)`
1168
-
1169
- Injects multiple services at once.
1170
-
1171
- ```tsx
1172
- const [service1, service2] = useInjectMany(Service1, Service2);
1173
- ```
1174
-
1175
- #### `hookFactory({ use, inject })`
1176
-
1177
- Creates a custom hook with injected dependencies.
1178
-
1179
- **Parameters:**
1180
-
1181
- - `use`: Hook function that receives parameters and `deps` array
1182
- - `inject`: Array of provider tokens to inject
1183
-
1184
- **Type signature:**
1185
-
1186
- ```tsx
1187
- function hookFactory<P extends HookParams, D extends any[], T>({
1188
- use: (params: HookWithDeps<P, D>) => T,
1189
- inject: ProviderToken[],
1190
- }): (params: P) => T
1191
- ```
1192
-
1193
- **Usage:**
1194
-
1195
- ```tsx
1196
- import type { HookWithDeps } from '@adimm/x-injection-reactjs';
1197
-
1198
- // Hook with no parameters
1199
- const useTestHook = hookFactory({
1200
- use: ({ deps: [testService] }: HookWithDeps<void, [TestService]>) => {
1201
- return testService.value;
1202
- },
1203
- inject: [TestService],
1204
- });
1205
-
1206
- // Hook with parameters
1207
- const useUserData = hookFactory({
1208
- use: ({ userId, deps: [apiService] }: HookWithDeps<{ userId: number }, [ApiService]>) => {
1209
- const [data, setData] = useState(null);
1210
- useEffect(() => {
1211
- apiService.get(`/users/${userId}`).then(setData);
1212
- }, [userId]);
1213
- return data;
1214
- },
1215
- inject: [ApiService],
1216
- });
1217
-
1218
- // Usage in components
1219
- const value = useTestHook(); // No params
1220
- const data = useUserData({ userId: 123 }); // With params
1221
- ```
1222
-
1223
- **`HookWithDeps<P, D>` type utility:**
1224
-
1225
- - **`P`**: Hook parameter type (`void` for no parameters, or object type for parameters)
1226
- - **`D`**: Tuple type matching your `inject` array (e.g., `[ApiService, AuthService]`)
1227
- - Automatically merges your parameters with the injected `deps` array
1228
-
1229
- #### `@Injectable()`
1230
-
1231
- Decorator that marks a class as injectable.
1232
-
1233
- ```tsx
1234
- @Injectable()
1235
- class MyService {
1236
- // Service implementation
1237
- }
1238
- ```
1239
-
1240
- #### `ProviderModule.blueprint(definition)`
1241
-
1242
- Creates a module **blueprint** (template) that can be instantiated multiple times. Use this for components that can have multiple instances.
1243
-
1244
- ```tsx
1245
- const MyModuleBp = ProviderModule.blueprint({
1246
- id: 'MyModule',
1247
- providers: [MyService],
1248
- imports: [OtherModuleBp],
1249
- exports: [MyService],
1250
- });
1251
- ```
1252
-
1253
- **When to use:** Components that can have multiple instances (forms, lists, dialogs, etc.).
1254
-
1255
- #### `ProviderModule.create(definition)`
1256
-
1257
- Creates and immediately initializes a module instance. In React apps, **you rarely need this**—use `blueprint()` instead.
1258
-
1259
- ```tsx
1260
- const MyModule = ProviderModule.create({
1261
- id: 'MyModule',
1262
- providers: [MyService],
1263
- });
1264
-
1265
- // Access the module directly and use it as a service locator.
1266
- const service = MyModule.get(MyService);
1267
- ```
1268
-
1269
- **When to use:** Direct module access outside of components, testing, or advanced scenarios. For normal React apps, use `blueprint()` for both global and component-scoped modules.
1270
-
1271
- **Key difference:**
1272
-
1273
- - `blueprint()`: Template - can create many instances. Use for **all** React modules (global and component-scoped).
1274
- - `create()`: Immediate instance - for direct access outside React components.
1275
-
1276
1076
  ## FAQ
1277
1077
 
1278
1078
  ### How do I add global services?
1279
1079
 
1280
- There are two ways to add global services:
1281
-
1282
- **Method 1: Global Blueprint (Recommended)**
1283
-
1284
- Create a global blueprint in your app's entry point. The library automatically imports it into the built-in `AppModule`:
1285
-
1286
- ```tsx
1287
- // main.tsx
1288
-
1289
- import 'reflect-metadata';
1290
-
1291
- import { Injectable, ProviderModule } from '@adimm/x-injection';
1292
-
1293
- @Injectable()
1294
- class ApiService {
1295
- get(url: string) {
1296
- /* ... */
1297
- }
1298
- }
1299
-
1300
- // Automatically imported into AppModule
1301
- ProviderModule.blueprint({
1302
- id: 'AppBootstrapModule',
1303
- isGlobal: true,
1304
- providers: [ApiService],
1305
- exports: [ApiService], // Make it available everywhere
1306
- });
1307
- ```
1308
-
1309
- **Method 2: Dynamic Updates**
1080
+ **Recommended:** Use a global blueprint with `isGlobal: true` in your entry point — see [Quick Start](#quick-start) and [Modules: Organizing Dependencies](#2-modules-organizing-dependencies) for the full pattern.
1310
1081
 
1311
- For runtime additions, use the built-in `AppModule` directly:
1082
+ **For runtime additions**, use the built-in `AppModule` directly:
1312
1083
 
1313
1084
  ```tsx
1314
1085
  import { AppModule } from '@adimm/x-injection';
1315
1086
 
1316
- // Add providers dynamically
1317
1087
  AppModule.update.addProvider(ApiService, true); // true = also export
1318
1088
  ```
1319
1089
 
1320
- [!WARNING]
1321
-
1090
+ > [!WARNING]
1322
1091
  > The library provides a built-in `AppModule`. Don't create your own module named "AppModule"—use one of the methods above instead.
1323
1092
 
1324
1093
  ### When should I use global modules vs component-scoped modules?
1325
1094
 
1326
- See the decision guide in [Global vs Component-Scoped Services](#global-vs-component-scoped-services).
1095
+ **Global** (`isGlobal: true` + `exports`): API clients, auth state, routing, theme, toast notifications — accessed directly via `useInject` without a HoC.
1096
+
1097
+ **Component-scoped** (blueprint without `isGlobal`): Form state, component-specific business logic, UI state — must use `provideModuleToComponent`; each instance gets its own module.
1327
1098
 
1328
1099
  ### Can I use this with Redux/MobX/Zustand?
1329
1100
 
@@ -1356,36 +1127,11 @@ class TodoStore {
1356
1127
  | Testability | ✅ | ⚠️ |
1357
1128
  | TypeScript support | ✅ | ⚠️ |
1358
1129
 
1359
- ### Do I need to understand Dependency Injection to use this?
1360
-
1361
- No! Think of it as a better way to organize your code:
1362
-
1363
- 1. **Services** = Your business logic (API calls, state, validation)
1364
- 2. **Modules** = Packages of services
1365
- 3. **Inject** = Get a service in your component
1366
-
1367
- That's it. The fancy terms (IoC, DI, containers) describe what's happening under the hood, but you don't need to understand them to be productive.
1368
-
1369
1130
  ### If I want Angular patterns, why not just use Angular?
1370
1131
 
1371
- Fair question! You shouldn't blindly adopt Angular patterns in React. Here's when this library makes sense:
1372
-
1373
- **Use xInjection if:**
1374
-
1375
- - You have complex business logic that's hard to test
1376
- - You're building enterprise apps with many modules/features
1377
- - You need component-scoped services (multiple instances of same component)
1378
- - You want class-based services (better for complex logic than hooks)
1379
- - You're migrating from Angular and want familiar patterns
1380
-
1381
- **Don't use xInjection if:**
1132
+ Because you want React's component model, hooks, and ecosystem — but need better architecture for complex business logic. xInjection brings IoC/DI to React without the framework lock-in.
1382
1133
 
1383
- - Your app is simple (Context + hooks is fine)
1384
- - You prefer functional programming over classes
1385
- - Your team isn't comfortable with IoC/DI
1386
- - You're building a small project
1387
-
1388
- **Why not just use Angular?** Because you love React's component model, hooks, and ecosystem, but you need better architecture for complex business logic. This gives you the best of both worlds.
1134
+ That said, if your app is simple, React Context + hooks is perfectly fine. xInjection shines in larger codebases with complex business logic, many modules, or a need for component-scoped service instances.
1389
1135
 
1390
1136
  ### Can I migrate gradually from an existing React app?
1391
1137
 
@@ -1399,45 +1145,11 @@ You can use xInjection alongside Context, Redux, or any other state management.
1399
1145
 
1400
1146
  ### When do I actually need `provideModuleToComponent`?
1401
1147
 
1402
- This is a common point of confusion. Here's a simple decision tree:
1403
-
1404
- **Don't need it (just use `useInject`):**
1405
-
1406
- - All your services are global/singleton
1407
- - Example: API client, auth service, theme service
1148
+ **Don't need it (just use `useInject`):** All your services are global/singleton — API client, auth service, theme service.
1408
1149
 
1409
- **Need it (must use `provideModuleToComponent`):**
1150
+ **Need it:** You want multiple independent component instances (forms, modals, dialogs), or parent needs to control child dependencies via the `inject` prop.
1410
1151
 
1411
- - You want multiple instances of a component, each with its own services
1412
- - Component-specific state that shouldn't be global
1413
- - Forms, modals, dialogs, reusable widgets
1414
- - Parent needs to control child dependencies via `inject` prop
1415
-
1416
- **Example:**
1417
-
1418
- ```tsx
1419
- // Global - NO provideModuleToComponent needed
1420
- ProviderModule.blueprint({
1421
- id: 'AppBootstrapModule',
1422
- isGlobal: true,
1423
- providers: [ApiService],
1424
- exports: [ApiService],
1425
- });
1426
-
1427
- function MyComponent() {
1428
- const apiService = useInject(ApiService); // Works! No HoC needed
1429
- }
1430
-
1431
- // Component-scoped - MUST use provideModuleToComponent
1432
- const FormModuleBp = ProviderModule.blueprint({
1433
- id: 'Form',
1434
- providers: [FormService],
1435
- });
1436
-
1437
- const Form = provideModuleToComponent(FormModuleBp, () => {
1438
- const formService = useInject(FormService); // Each <Form /> gets its own FormService
1439
- });
1440
- ```
1152
+ See [Why Use the HoC Approach?](#why-use-the-hoc-approach) for a full explanation.
1441
1153
 
1442
1154
  ### What's the performance impact?
1443
1155
 
@@ -1450,21 +1162,6 @@ Minimal. The dependency container is lightweight, and services are created lazil
1450
1162
  - You get runtime debugging and introspection
1451
1163
  - Works with all bundlers/tools without special configuration
1452
1164
 
1453
- ### Is this production-ready?
1454
-
1455
- Yes! This library is inspired by battle-tested patterns from Angular and NestJS, adapted for React.
1456
-
1457
- ### Is "provider hell" really that bad?
1458
-
1459
- You're right to question this! Provider hell becomes a real problem when:
1460
-
1461
- - You need to pass providers down through component trees
1462
- - Different parts of your app need different provider configurations
1463
- - You want multiple instances of the same component with isolated state
1464
- - You're constantly adding/removing providers as features change
1465
-
1466
- If you're happy with your current Context setup, stick with it! This library is for teams that have **outgrown** simple Context patterns.
1467
-
1468
1165
  ### Why use classes for services instead of custom hooks?
1469
1166
 
1470
1167
  Both approaches work! Here's when classes shine:
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/AdiMarianMutu/x-injection-reactjs"
6
6
  },
7
7
  "description": "ReactJS integration of the `xInjection` library.",
8
- "version": "1.0.5",
8
+ "version": "1.0.6",
9
9
  "author": "Adi-Marian Mutu",
10
10
  "homepage": "https://github.com/AdiMarianMutu/x-injection-reactjs#readme",
11
11
  "bugs": "https://github.com/AdiMarianMutu/x-injection-reactjs/issues",
@@ -42,7 +42,7 @@
42
42
  "v:bump-major": "npm version major -m \"chore: update lib major version %s\""
43
43
  },
44
44
  "dependencies": {
45
- "@adimm/x-injection": "^3.0.0",
45
+ "@adimm/x-injection": "^3.0.4",
46
46
  "react": ">=18.0.0"
47
47
  },
48
48
  "devDependencies": {