0
# Core Services and Dependency Injection
1
2
Essential services for component I/O management, reflection utilities, and dependency injection infrastructure that power the ng-dynamic-component library.
3
4
## Capabilities
5
6
### Component I/O Management
7
8
Abstract service for managing component inputs and outputs with extensible implementation.
9
10
```typescript { .api }
11
/**
12
* Abstract service for setting inputs and getting outputs on dynamic components
13
* Provides extensible interface for custom I/O management implementations
14
*/
15
@Injectable({
16
providedIn: 'root',
17
useClass: ClassicComponentIO
18
})
19
export abstract class ComponentIO {
20
/**
21
* Sets an input property on a component
22
* @param componentRef - Reference to the target component
23
* @param name - Name of the input property (must be a valid component input)
24
* @param value - Value to set for the input
25
*/
26
abstract setInput<T, K extends ComponentInputKey<T>>(
27
componentRef: ComponentRef<T>,
28
name: K,
29
value: T[K]
30
): void;
31
32
/**
33
* Gets an output observable from a component
34
* @param componentRef - Reference to the target component
35
* @param name - Name of the output property
36
* @returns Observable that emits when the output event occurs
37
*/
38
abstract getOutput<T, K extends ComponentInputKey<T>>(
39
componentRef: ComponentRef<T>,
40
name: K
41
): Observable<unknown>;
42
}
43
44
/**
45
* Type constraint for component input property names
46
* Ensures type safety when accessing component properties
47
*/
48
type ComponentInputKey<T> = keyof T & string;
49
```
50
51
### I/O Factory Service
52
53
Factory service for creating and configuring IoService instances with custom options.
54
55
```typescript { .api }
56
/**
57
* Factory for creating IoService instances with custom configuration
58
* Enables per-component customization of I/O behavior
59
*/
60
@Injectable({ providedIn: 'root' })
61
export class IoFactoryService {
62
/**
63
* Creates a new IoService instance with specified configuration
64
* @param componentInjector - Injector that provides ComponentRef access
65
* @param ioOptions - Combined configuration options for the service
66
* @returns Configured IoService instance ready for use
67
*/
68
create(
69
componentInjector: DynamicComponentInjector,
70
ioOptions?: IoServiceOptions & IoFactoryServiceOptions
71
): IoService;
72
}
73
74
/**
75
* Additional options specific to IoFactoryService.create method
76
*/
77
interface IoFactoryServiceOptions {
78
/** Optional custom injector for dependency resolution */
79
injector?: Injector;
80
}
81
```
82
83
### I/O Service Configuration
84
85
Configuration service for customizing I/O service behavior.
86
87
```typescript { .api }
88
/**
89
* Configuration options for IoService instances
90
* Controls various aspects of input/output management behavior
91
*/
92
@Injectable({ providedIn: 'root' })
93
export class IoServiceOptions {
94
/**
95
* Whether to track output changes for performance optimization
96
* When enabled, prevents unnecessary subscription updates
97
*/
98
trackOutputChanges: boolean = false;
99
}
100
101
/**
102
* Core service for managing dynamic inputs and outputs on components
103
* Handles property binding, event subscription, and lifecycle management
104
*/
105
@Injectable()
106
export class IoService implements OnDestroy {
107
/**
108
* Updates component inputs and outputs
109
* Applies new input values and manages output event subscriptions
110
* @param inputs - Object mapping input names to values (null to clear)
111
* @param outputs - Object mapping output names to handlers (null to clear)
112
*/
113
update(inputs?: InputsType | null, outputs?: OutputsType | null): void;
114
}
115
```
116
117
**Usage Examples:**
118
119
```typescript
120
import { Component, Injectable, Injector } from '@angular/core';
121
import {
122
IoFactoryService,
123
IoService,
124
IoServiceOptions,
125
DynamicComponentInjector,
126
ComponentIO
127
} from 'ng-dynamic-component';
128
129
// Custom I/O service configuration
130
@Injectable()
131
export class CustomIoServiceOptions extends IoServiceOptions {
132
trackOutputChanges = true; // Enable output change tracking
133
134
// Add custom configuration properties
135
debugMode = false;
136
logInputChanges = true;
137
}
138
139
// Using IoFactoryService for custom I/O management
140
@Component({
141
template: `
142
<ndc-dynamic
143
[ndcDynamicComponent]="component"
144
#dynamicComponent="ndcDynamicIo">
145
</ndc-dynamic>
146
147
<button (click)="createCustomIoService(dynamicComponent)">
148
Create Custom I/O Service
149
</button>
150
`
151
})
152
export class CustomIoServiceComponent {
153
component = MyComponent;
154
155
constructor(
156
private ioFactory: IoFactoryService,
157
private injector: Injector
158
) {}
159
160
createCustomIoService(componentInjector: DynamicComponentInjector) {
161
// Create custom I/O service with specific configuration
162
const customIoService = this.ioFactory.create(componentInjector, {
163
trackOutputChanges: true,
164
injector: this.injector
165
});
166
167
// Use the custom service
168
customIoService.update(
169
{ title: 'Custom I/O Service', data: [1, 2, 3] },
170
{ onSave: (data: any) => console.log('Custom save:', data) }
171
);
172
}
173
}
174
175
// Advanced I/O service usage with lifecycle management
176
@Component({
177
template: `
178
<div class="io-controls">
179
<button (click)="startManualIoManagement()">Start Manual I/O</button>
180
<button (click)="stopManualIoManagement()">Stop Manual I/O</button>
181
<button (click)="updateInputs()">Update Inputs</button>
182
</div>
183
184
<ndc-dynamic
185
[ndcDynamicComponent]="component"
186
#componentRef="ndcComponentOutletInjector">
187
</ndc-dynamic>
188
`
189
})
190
export class ManualIoManagementComponent implements OnDestroy {
191
component = DataComponent;
192
193
private manualIoService?: IoService;
194
private inputUpdateInterval?: number;
195
196
constructor(private ioFactory: IoFactoryService) {}
197
198
startManualIoManagement() {
199
if (this.manualIoService) {
200
return; // Already started
201
}
202
203
// Create I/O service with custom options
204
this.manualIoService = this.ioFactory.create(
205
{ componentRef: this.getComponentRef() },
206
{
207
trackOutputChanges: true,
208
injector: this.getCustomInjector()
209
}
210
);
211
212
// Set up initial I/O
213
this.manualIoService.update(
214
{ title: 'Manual Management', refreshRate: 1000 },
215
{
216
onDataChange: (data: any) => this.handleDataChange(data),
217
onError: (error: any) => this.handleError(error)
218
}
219
);
220
221
// Set up periodic input updates
222
this.inputUpdateInterval = window.setInterval(() => {
223
this.updateInputs();
224
}, 2000);
225
226
console.log('Manual I/O management started');
227
}
228
229
stopManualIoManagement() {
230
if (this.manualIoService) {
231
// Clear inputs and outputs
232
this.manualIoService.update(null, null);
233
this.manualIoService = undefined;
234
}
235
236
if (this.inputUpdateInterval) {
237
clearInterval(this.inputUpdateInterval);
238
this.inputUpdateInterval = undefined;
239
}
240
241
console.log('Manual I/O management stopped');
242
}
243
244
updateInputs() {
245
if (this.manualIoService) {
246
this.manualIoService.update({
247
title: `Updated at ${new Date().toLocaleTimeString()}`,
248
data: Array.from({ length: 5 }, () => Math.floor(Math.random() * 100)),
249
timestamp: Date.now()
250
});
251
}
252
}
253
254
ngOnDestroy() {
255
this.stopManualIoManagement();
256
}
257
258
private getComponentRef() {
259
// Implementation to get component reference
260
return null; // Placeholder
261
}
262
263
private getCustomInjector() {
264
// Create custom injector with additional providers
265
return Injector.create({
266
providers: [
267
{ provide: 'CUSTOM_CONFIG', useValue: { apiUrl: '/api' } }
268
]
269
});
270
}
271
272
private handleDataChange(data: any) {
273
console.log('Data changed:', data);
274
}
275
276
private handleError(error: any) {
277
console.error('Component error:', error);
278
}
279
}
280
```
281
282
### Reflection Services
283
284
Services for accessing component metadata and reflection information.
285
286
```typescript { .api }
287
/**
288
* Contract for Reflect API subsystem required by the library
289
* Abstracts the reflection capabilities needed for metadata access
290
*/
291
interface ReflectApi {
292
/**
293
* Gets metadata from an object using reflection
294
* @param type - The metadata type key to retrieve
295
* @param obj - The object to get metadata from
296
* @returns Array of metadata values
297
*/
298
getMetadata(type: string, obj: unknown): unknown[];
299
}
300
301
/**
302
* Injection token for ReflectApi implementation
303
* Provides access to reflection capabilities
304
* Default factory returns window.Reflect
305
*/
306
const ReflectRef: InjectionToken<ReflectApi>;
307
308
/**
309
* Service for accessing reflection metadata on classes and objects
310
* Used internally for component analysis and dependency injection
311
*/
312
@Injectable({ providedIn: 'root' })
313
export class ReflectService {
314
/**
315
* Gets constructor parameter types using reflection
316
* @param ctor - Constructor function to analyze
317
* @returns Array of parameter types
318
*/
319
getCtorParamTypes(ctor: Type<unknown>): unknown[];
320
}
321
```
322
323
**Usage Examples:**
324
325
```typescript
326
import { Injectable, Type } from '@angular/core';
327
import { ReflectService, ReflectApi, ReflectRef } from 'ng-dynamic-component';
328
329
// Custom reflection implementation
330
@Injectable()
331
export class CustomReflectApi implements ReflectApi {
332
getMetadata(type: string, obj: unknown): unknown[] {
333
// Custom metadata retrieval logic
334
if (typeof obj === 'function' && (obj as any).__metadata__) {
335
return (obj as any).__metadata__[type] || [];
336
}
337
return [];
338
}
339
}
340
341
// Using reflection service for component analysis
342
@Injectable({ providedIn: 'root' })
343
export class ComponentAnalyzer {
344
constructor(private reflectService: ReflectService) {}
345
346
analyzeComponent<T>(componentType: Type<T>): ComponentMetadata {
347
// Get constructor parameter types
348
const paramTypes = this.reflectService.getCtorParamTypes(componentType);
349
350
console.log('Component constructor parameters:', paramTypes);
351
352
return {
353
name: componentType.name,
354
parameterTypes: paramTypes,
355
hasCustomInjection: paramTypes.length > 0
356
};
357
}
358
}
359
360
interface ComponentMetadata {
361
name: string;
362
parameterTypes: unknown[];
363
hasCustomInjection: boolean;
364
}
365
366
// Providing custom reflection implementation
367
@Component({
368
providers: [
369
{ provide: ReflectRef, useClass: CustomReflectApi }
370
],
371
template: `<!-- Component with custom reflection -->`
372
})
373
export class CustomReflectionComponent {
374
constructor(private componentAnalyzer: ComponentAnalyzer) {
375
// Analyze various component types
376
this.analyzeComponents();
377
}
378
379
private analyzeComponents() {
380
const componentsToAnalyze = [
381
MyComponent,
382
AnotherComponent,
383
ComplexComponent
384
];
385
386
componentsToAnalyze.forEach(component => {
387
const metadata = this.componentAnalyzer.analyzeComponent(component);
388
console.log('Component analysis:', metadata);
389
});
390
}
391
}
392
```
393
394
### Dependency Injection Infrastructure
395
396
Core interfaces and tokens for dependency injection throughout the library.
397
398
```typescript { .api }
399
/**
400
* Interface for objects that can provide access to ComponentRef instances
401
* Core abstraction for component reference access across the library
402
*/
403
interface DynamicComponentInjector {
404
/** Reference to the dynamic component instance (null if not created) */
405
componentRef: ComponentRef<unknown> | null;
406
}
407
408
/**
409
* Angular DI token for injecting DynamicComponentInjector instances
410
* Used throughout the library for accessing component references
411
*/
412
const DynamicComponentInjectorToken: InjectionToken<DynamicComponentInjector>;
413
```
414
415
**Usage Examples:**
416
417
```typescript
418
import { Component, Inject, Injectable } from '@angular/core';
419
import { DynamicComponentInjector, DynamicComponentInjectorToken } from 'ng-dynamic-component';
420
421
// Service that works with dynamic components
422
@Injectable()
423
export class DynamicComponentService {
424
constructor(
425
@Inject(DynamicComponentInjectorToken)
426
private componentInjector: DynamicComponentInjector
427
) {}
428
429
getComponentInstance<T>(): T | null {
430
const ref = this.componentInjector.componentRef;
431
return ref ? (ref.instance as T) : null;
432
}
433
434
executeOnComponent<T>(action: (instance: T) => void): boolean {
435
const instance = this.getComponentInstance<T>();
436
if (instance) {
437
action(instance);
438
return true;
439
}
440
return false;
441
}
442
443
triggerComponentMethod(methodName: string, ...args: any[]): any {
444
const instance = this.getComponentInstance();
445
if (instance && typeof (instance as any)[methodName] === 'function') {
446
return (instance as any)[methodName](...args);
447
}
448
return null;
449
}
450
}
451
452
// Custom component injector implementation
453
@Injectable()
454
export class CustomComponentInjector implements DynamicComponentInjector {
455
private _componentRef: ComponentRef<unknown> | null = null;
456
457
get componentRef(): ComponentRef<unknown> | null {
458
return this._componentRef;
459
}
460
461
setComponentRef(ref: ComponentRef<unknown> | null) {
462
this._componentRef = ref;
463
}
464
}
465
466
// Component using dependency injection infrastructure
467
@Component({
468
providers: [
469
DynamicComponentService,
470
{ provide: DynamicComponentInjectorToken, useClass: CustomComponentInjector }
471
],
472
template: `
473
<div class="component-controls">
474
<button (click)="executeAction()">Execute Action</button>
475
<button (click)="callMethod()">Call Method</button>
476
<button (click)="getInfo()">Get Component Info</button>
477
</div>
478
479
<ndc-dynamic
480
[ndcDynamicComponent]="component"
481
(ndcDynamicCreated)="onComponentCreated($event)">
482
</ndc-dynamic>
483
`
484
})
485
export class DependencyInjectionExampleComponent {
486
component = InteractiveComponent;
487
488
constructor(
489
private dynamicService: DynamicComponentService,
490
@Inject(DynamicComponentInjectorToken)
491
private componentInjector: DynamicComponentInjector
492
) {}
493
494
onComponentCreated(componentRef: ComponentRef<any>) {
495
// Update the injector with the new component reference
496
if (this.componentInjector instanceof CustomComponentInjector) {
497
this.componentInjector.setComponentRef(componentRef);
498
}
499
}
500
501
executeAction() {
502
this.dynamicService.executeOnComponent<any>(instance => {
503
console.log('Executing action on:', instance.constructor.name);
504
if (instance.performAction) {
505
instance.performAction('custom-action');
506
}
507
});
508
}
509
510
callMethod() {
511
const result = this.dynamicService.triggerComponentMethod('getData');
512
console.log('Method result:', result);
513
}
514
515
getInfo() {
516
const instance = this.dynamicService.getComponentInstance();
517
console.log('Component instance:', instance);
518
console.log('Component type:', instance?.constructor.name);
519
}
520
}
521
```
522
523
### Advanced Service Integration
524
525
Complex scenarios involving multiple services and custom configurations.
526
527
```typescript
528
// Multi-service integration with custom configuration
529
@Injectable({ providedIn: 'root' })
530
export class AdvancedDynamicComponentManager {
531
constructor(
532
private ioFactory: IoFactoryService,
533
private reflectService: ReflectService,
534
private componentIO: ComponentIO
535
) {}
536
537
createManagedComponent<T>(
538
componentType: Type<T>,
539
container: ViewContainerRef,
540
config: ManagedComponentConfig<T>
541
): ManagedComponentRef<T> {
542
// Analyze component using reflection
543
const metadata = this.analyzeComponent(componentType);
544
545
// Create component with custom injector
546
const componentRef = container.createComponent(componentType, {
547
injector: config.injector
548
});
549
550
// Create custom I/O service
551
const ioService = this.ioFactory.create(
552
{ componentRef },
553
config.ioOptions
554
);
555
556
// Set up inputs and outputs
557
if (config.inputs) {
558
ioService.update(config.inputs, null);
559
}
560
if (config.outputs) {
561
ioService.update(null, config.outputs);
562
}
563
564
return new ManagedComponentRef(componentRef, ioService, metadata);
565
}
566
567
private analyzeComponent<T>(componentType: Type<T>) {
568
return {
569
parameterTypes: this.reflectService.getCtorParamTypes(componentType),
570
name: componentType.name
571
};
572
}
573
}
574
575
interface ManagedComponentConfig<T> {
576
injector?: Injector;
577
inputs?: InputsType;
578
outputs?: OutputsType;
579
ioOptions?: IoServiceOptions;
580
}
581
582
class ManagedComponentRef<T> {
583
constructor(
584
public componentRef: ComponentRef<T>,
585
public ioService: IoService,
586
public metadata: any
587
) {}
588
589
updateIO(inputs?: InputsType, outputs?: OutputsType) {
590
this.ioService.update(inputs, outputs);
591
}
592
593
destroy() {
594
this.ioService.update(null, null); // Clear I/O
595
this.componentRef.destroy();
596
}
597
}
598
```
599
600
### Event Context System
601
602
Tokens for managing custom contexts in output event handlers.
603
604
```typescript { .api }
605
/**
606
* Factory function for the default event argument token value
607
* @returns The string '$event' used as the default event argument placeholder
608
*/
609
function defaultEventArgumentFactory(): string;
610
611
/**
612
* Injection token for customizing the event argument placeholder string
613
* Default value is '$event', can be overridden for custom event handling
614
*/
615
const IoEventArgumentToken: InjectionToken<string>;
616
617
/**
618
* @deprecated Since v10.4.0 - Use IoEventArgumentToken instead
619
*/
620
const EventArgumentToken: InjectionToken<string>;
621
622
/**
623
* Injection token for providing custom context objects to output handlers
624
* Allows output functions to be bound to specific context objects
625
*/
626
const IoEventContextToken: InjectionToken<unknown>;
627
628
/**
629
* Injection token for providing custom context provider configuration
630
* Used to configure how IoEventContextToken should be resolved
631
*/
632
const IoEventContextProviderToken: InjectionToken<StaticProvider>;
633
```
634
635
**Usage Examples:**
636
637
```typescript
638
import { Component, Injectable, InjectionToken } from '@angular/core';
639
import {
640
IoEventContextToken,
641
IoEventContextProviderToken,
642
IoEventArgumentToken,
643
OutputsType
644
} from 'ng-dynamic-component';
645
646
// Custom event context implementation
647
@Injectable()
648
export class CustomEventContext {
649
private apiService = inject(ApiService);
650
private logger = inject(LoggerService);
651
652
async handleSaveEvent(data: any, eventInfo: any) {
653
this.logger.log('Save event triggered:', { data, eventInfo });
654
655
try {
656
const result = await this.apiService.save(data);
657
this.logger.log('Save successful:', result);
658
return result;
659
} catch (error) {
660
this.logger.error('Save failed:', error);
661
throw error;
662
}
663
}
664
665
handleDeleteEvent(id: string) {
666
return this.apiService.delete(id);
667
}
668
}
669
670
// Component using custom event context
671
@Component({
672
providers: [
673
CustomEventContext,
674
{
675
provide: IoEventContextProviderToken,
676
useValue: { provide: IoEventContextToken, useClass: CustomEventContext }
677
}
678
],
679
template: `
680
<ndc-dynamic
681
[ndcDynamicComponent]="component"
682
[ndcDynamicInputs]="inputs"
683
[ndcDynamicOutputs]="outputs">
684
</ndc-dynamic>
685
`
686
})
687
export class EventContextExampleComponent {
688
component = DataFormComponent;
689
690
inputs = {
691
title: 'Event Context Demo',
692
data: { name: 'John', email: 'john@example.com' }
693
};
694
695
// Output handlers will be bound to CustomEventContext instance
696
outputs: OutputsType = {
697
// Handler method will be called with CustomEventContext as 'this'
698
onSave: function(this: CustomEventContext, data: any) {
699
return this.handleSaveEvent(data, { timestamp: Date.now() });
700
},
701
702
onDelete: function(this: CustomEventContext, id: string) {
703
return this.handleDeleteEvent(id);
704
},
705
706
// Event argument customization
707
onValidate: {
708
handler: function(this: CustomEventContext, formData: any, customArg: string) {
709
console.log('Validation with custom context:', { formData, customArg });
710
return formData && Object.keys(formData).length > 0;
711
},
712
args: ['$event', 'validation-context'] // '$event' will be replaced with actual event
713
}
714
};
715
}
716
717
// Global event context configuration
718
@Component({
719
providers: [
720
// Custom event argument placeholder
721
{ provide: IoEventArgumentToken, useValue: '$data' },
722
723
// Global event context
724
{ provide: IoEventContextToken, useClass: GlobalEventContext }
725
],
726
template: `
727
<div class="global-context-demo">
728
<ndc-dynamic
729
[ndcDynamicComponent]="component"
730
[ndcDynamicOutputs]="globalOutputs">
731
</ndc-dynamic>
732
</div>
733
`
734
})
735
export class GlobalEventContextComponent {
736
component = NotificationComponent;
737
738
globalOutputs: OutputsType = {
739
// Using custom event argument placeholder '$data'
740
onNotify: {
741
handler: (message: string, level: string) => {
742
console.log(`[${level.toUpperCase()}] ${message}`);
743
},
744
args: ['$data', 'info'] // '$data' matches IoEventArgumentToken value
745
},
746
747
// Direct function (will be bound to GlobalEventContext)
748
onDismiss: function(this: GlobalEventContext) {
749
this.handleDismiss();
750
}
751
};
752
}
753
754
@Injectable()
755
export class GlobalEventContext {
756
handleDismiss() {
757
console.log('Notification dismissed via global context');
758
}
759
}
760
```
761
762
### Module Exports
763
764
NgModule classes for organizing and importing library functionality.
765
766
```typescript { .api }
767
/**
768
* Main module that includes all dynamic component functionality
769
* Exports DynamicComponent and DynamicIoModule
770
*/
771
@NgModule({
772
imports: [DynamicIoModule, DynamicComponent],
773
exports: [DynamicIoModule, DynamicComponent]
774
})
775
export class DynamicModule {}
776
777
/**
778
* Module for dynamic I/O directives and related functionality
779
* Includes ComponentOutletInjectorModule
780
*/
781
@NgModule({
782
imports: [DynamicIoDirective],
783
exports: [DynamicIoDirective, ComponentOutletInjectorModule]
784
})
785
export class DynamicIoModule {}
786
787
/**
788
* Module for dynamic attributes directive
789
* Includes ComponentOutletInjectorModule for NgComponentOutlet support
790
*/
791
@NgModule({
792
imports: [DynamicAttributesDirective],
793
exports: [DynamicAttributesDirective, ComponentOutletInjectorModule]
794
})
795
export class DynamicAttributesModule {}
796
797
/**
798
* Module for experimental dynamic directives functionality
799
* Includes ComponentOutletInjectorModule
800
*/
801
@NgModule({
802
imports: [DynamicDirectivesDirective],
803
exports: [DynamicDirectivesDirective, ComponentOutletInjectorModule]
804
})
805
export class DynamicDirectivesModule {}
806
807
/**
808
* Module for NgComponentOutlet enhancement directives
809
* Provides component reference access and I/O capabilities
810
*/
811
@NgModule({
812
imports: [ComponentOutletInjectorDirective, ComponentOutletIoDirective],
813
exports: [ComponentOutletInjectorDirective, ComponentOutletIoDirective]
814
})
815
export class ComponentOutletInjectorModule {}
816
```