@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.
- package/README.md +174 -477
- 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
|
-
|
|
42
|
-
- [
|
|
43
|
-
- [
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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()`**:
|
|
435
|
-
- **`create()`**: Immediately
|
|
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
|
-
|
|
408
|
+
See [Module Imports and Exports](#module-imports-and-exports) for examples of both.
|
|
438
409
|
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
517
|
+
## Why Use the HoC Approach?
|
|
623
518
|
|
|
624
|
-
|
|
519
|
+
You might wonder: "Why wrap my component with `provideModuleToComponent` instead of just using `useInject` directly everywhere?"
|
|
625
520
|
|
|
626
|
-
|
|
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
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
551
|
+
## Hierarchical Dependency Injection
|
|
648
552
|
|
|
649
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
584
|
+
**Component example:**
|
|
656
585
|
|
|
657
586
|
```tsx
|
|
587
|
+
// ① Global services — live in AppModule, available everywhere
|
|
658
588
|
@Injectable()
|
|
659
|
-
class
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
593
|
+
ProviderModule.blueprint({
|
|
594
|
+
id: 'AppBootstrapModule',
|
|
595
|
+
isGlobal: true,
|
|
596
|
+
providers: [ApiService, AuthService],
|
|
597
|
+
exports: [ApiService, AuthService],
|
|
673
598
|
});
|
|
674
|
-
```
|
|
675
599
|
|
|
676
|
-
|
|
600
|
+
// ② Shared module — created once, imported into component blueprints
|
|
601
|
+
@Injectable()
|
|
602
|
+
class AnalyticsService {}
|
|
677
603
|
|
|
678
|
-
|
|
604
|
+
const SharedModule = ProviderModule.create({
|
|
605
|
+
id: 'SharedModule',
|
|
606
|
+
providers: [AnalyticsService],
|
|
607
|
+
exports: [AnalyticsService], // ✅ visible to importers
|
|
608
|
+
});
|
|
679
609
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
|
|
619
|
+
const DashboardModuleBp = ProviderModule.blueprint({
|
|
620
|
+
id: 'DashboardModule',
|
|
621
|
+
imports: [SharedModule],
|
|
622
|
+
providers: [DashboardService], // ① own container
|
|
685
623
|
});
|
|
686
|
-
```
|
|
687
624
|
|
|
688
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
//
|
|
797
|
-
const
|
|
720
|
+
// Instantiated once — all importers share the same instance and the same singletons
|
|
721
|
+
const CoreModule = ProviderModule.create({
|
|
798
722
|
id: 'CoreModule',
|
|
799
|
-
providers: [
|
|
800
|
-
exports: [
|
|
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: [
|
|
807
|
-
providers: [UserService],
|
|
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: [
|
|
735
|
+
imports: [CoreModule], // same CoreModule instance
|
|
814
736
|
providers: [ProductService],
|
|
815
737
|
});
|
|
816
738
|
```
|
|
817
739
|
|
|
818
|
-
**
|
|
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
|
|
822
|
-
id: '
|
|
823
|
-
imports: [
|
|
824
|
-
|
|
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
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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.
|
|
45
|
+
"@adimm/x-injection": "^3.0.4",
|
|
46
46
|
"react": ">=18.0.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|