0
# Dynamic Directive Injection (Experimental)
1
2
**⚠️ Experimental Feature**: Advanced system for dynamically attaching directives to components at runtime with input/output binding support. This API is experimental and has known limitations.
3
4
## Capabilities
5
6
### Dynamic Directives Directive
7
8
Main directive for creating and managing dynamic directives on components.
9
10
```typescript { .api }
11
/**
12
* Creates and manages dynamic directives on components (Experimental API)
13
* Note: OnChanges hook is not triggered on dynamic directives (known limitation)
14
*/
15
@Directive({
16
selector: '[ndcDynamicDirectives],[ngComponentOutletNdcDynamicDirectives]',
17
standalone: true
18
})
19
export class DynamicDirectivesDirective implements OnDestroy, DoCheck {
20
/** Array of directive definitions for ndc-dynamic components */
21
@Input() ndcDynamicDirectives?: DynamicDirectiveDef<unknown>[] | null;
22
23
/** Array of directive definitions for NgComponentOutlet components */
24
@Input() ngComponentOutletNdcDynamicDirectives?: DynamicDirectiveDef<unknown>[] | null;
25
26
/** Emitted when new directives are created */
27
@Output() ndcDynamicDirectivesCreated: EventEmitter<DirectiveRef<unknown>[]>;
28
}
29
```
30
31
### Dynamic Directives Module
32
33
NgModule that provides dynamic directives functionality for traditional module-based applications.
34
35
```typescript { .api }
36
/**
37
* Module for dynamic directives functionality
38
* Includes component outlet integration
39
*/
40
@NgModule({
41
imports: [DynamicDirectivesDirective, ComponentOutletInjectorModule],
42
exports: [DynamicDirectivesDirective, ComponentOutletInjectorModule]
43
})
44
export class DynamicDirectivesModule {}
45
```
46
47
## Types
48
49
### Directive Definition Types
50
51
```typescript { .api }
52
/**
53
* Definition object for dynamic directives
54
* Specifies the directive type and optional I/O bindings
55
*/
56
interface DynamicDirectiveDef<T> {
57
/** The directive class type to instantiate */
58
type: Type<T>;
59
/** Optional input bindings for the directive */
60
inputs?: InputsType;
61
/** Optional output bindings for the directive */
62
outputs?: OutputsType;
63
}
64
65
/**
66
* Helper function to create DynamicDirectiveDef objects with type safety
67
* @param type - The directive class
68
* @param inputs - Optional input bindings
69
* @param outputs - Optional output bindings
70
* @returns Typed directive definition
71
*/
72
function dynamicDirectiveDef<T>(
73
type: Type<T>,
74
inputs?: InputsType,
75
outputs?: OutputsType
76
): DynamicDirectiveDef<T>;
77
```
78
79
### Directive Reference Types
80
81
```typescript { .api }
82
/**
83
* Reference object for created dynamic directives (similar to ComponentRef)
84
* Provides access to the directive instance and lifecycle management
85
*/
86
interface DirectiveRef<T> {
87
/** The directive instance */
88
instance: T;
89
/** The directive class type */
90
type: Type<T>;
91
/** The injector used for the directive */
92
injector: Injector;
93
/** Reference to the host component */
94
hostComponent: unknown;
95
/** Reference to the host view */
96
hostView: ViewRef;
97
/** Element reference where directive is attached */
98
location: ElementRef;
99
/** Change detector reference for the directive */
100
changeDetectorRef: ChangeDetectorRef;
101
/** Method to register destroy callbacks */
102
onDestroy: (callback: Function) => void;
103
}
104
```
105
106
**Usage Examples:**
107
108
```typescript
109
import { Component, Directive, Input, Output, EventEmitter } from '@angular/core';
110
import {
111
DynamicComponent,
112
DynamicDirectivesDirective,
113
DynamicDirectiveDef,
114
DirectiveRef,
115
dynamicDirectiveDef
116
} from 'ng-dynamic-component';
117
118
// Example directives to attach dynamically
119
@Directive({
120
selector: '[tooltipDirective]',
121
standalone: true
122
})
123
export class TooltipDirective {
124
@Input() tooltip = '';
125
@Input() position: 'top' | 'bottom' | 'left' | 'right' = 'top';
126
@Output() tooltipShow = new EventEmitter<void>();
127
@Output() tooltipHide = new EventEmitter<void>();
128
129
// Directive implementation...
130
}
131
132
@Directive({
133
selector: '[highlightDirective]',
134
standalone: true
135
})
136
export class HighlightDirective {
137
@Input() highlightColor = 'yellow';
138
@Input() highlightDuration = 1000;
139
@Output() highlightStart = new EventEmitter<void>();
140
@Output() highlightEnd = new EventEmitter<void>();
141
142
// Directive implementation...
143
}
144
145
// Basic dynamic directive usage
146
@Component({
147
standalone: true,
148
imports: [DynamicComponent, DynamicDirectivesDirective],
149
template: `
150
<ndc-dynamic
151
[ndcDynamicComponent]="component"
152
[ndcDynamicDirectives]="directives"
153
(ndcDynamicDirectivesCreated)="onDirectivesCreated($event)">
154
</ndc-dynamic>
155
`
156
})
157
export class BasicDynamicDirectivesComponent {
158
component = MyComponent;
159
160
directives: DynamicDirectiveDef<unknown>[] = [
161
{
162
type: TooltipDirective,
163
inputs: {
164
tooltip: 'This is a dynamic tooltip',
165
position: 'top'
166
},
167
outputs: {
168
tooltipShow: () => console.log('Tooltip shown'),
169
tooltipHide: () => console.log('Tooltip hidden')
170
}
171
},
172
{
173
type: HighlightDirective,
174
inputs: {
175
highlightColor: 'lightblue',
176
highlightDuration: 2000
177
},
178
outputs: {
179
highlightStart: () => console.log('Highlight started'),
180
highlightEnd: () => console.log('Highlight ended')
181
}
182
}
183
];
184
185
onDirectivesCreated(directiveRefs: DirectiveRef<unknown>[]) {
186
console.log('Created directives:', directiveRefs);
187
188
// Access directive instances
189
directiveRefs.forEach(ref => {
190
console.log('Directive type:', ref.type.name);
191
console.log('Directive instance:', ref.instance);
192
});
193
}
194
}
195
196
// Using dynamicDirectiveDef helper for type safety
197
@Component({
198
template: `
199
<ndc-dynamic
200
[ndcDynamicComponent]="component"
201
[ndcDynamicDirectives]="typeSafeDirectives">
202
</ndc-dynamic>
203
`
204
})
205
export class TypeSafeDynamicDirectivesComponent {
206
component = ContentComponent;
207
208
typeSafeDirectives = [
209
// Type-safe directive definitions
210
dynamicDirectiveDef(TooltipDirective, {
211
tooltip: 'Type-safe tooltip',
212
position: 'bottom'
213
}, {
214
tooltipShow: () => this.handleTooltipShow(),
215
tooltipHide: () => this.handleTooltipHide()
216
}),
217
218
dynamicDirectiveDef(HighlightDirective, {
219
highlightColor: 'lightgreen'
220
})
221
];
222
223
handleTooltipShow() {
224
console.log('Tooltip is now visible');
225
}
226
227
handleTooltipHide() {
228
console.log('Tooltip is now hidden');
229
}
230
}
231
```
232
233
### Dynamic Directive Management
234
235
Manage directives dynamically based on application state:
236
237
```typescript
238
@Component({
239
template: `
240
<div class="controls">
241
<label>
242
<input type="checkbox" [(ngModel)]="enableTooltip" />
243
Enable Tooltip
244
</label>
245
<label>
246
<input type="checkbox" [(ngModel)]="enableHighlight" />
247
Enable Highlight
248
</label>
249
<label>
250
<input type="checkbox" [(ngModel)]="enableCustomDirective" />
251
Enable Custom Directive
252
</label>
253
</div>
254
255
<ndc-dynamic
256
[ndcDynamicComponent]="component"
257
[ndcDynamicDirectives]="activeDirectives"
258
(ndcDynamicDirectivesCreated)="trackDirectives($event)">
259
</ndc-dynamic>
260
261
<div class="directive-info">
262
<h3>Active Directives: {{ activeDirectiveRefs.length }}</h3>
263
<ul>
264
<li *ngFor="let ref of activeDirectiveRefs">
265
{{ ref.type.name }}
266
</li>
267
</ul>
268
</div>
269
`
270
})
271
export class ManagedDynamicDirectivesComponent {
272
component = InteractiveComponent;
273
274
enableTooltip = true;
275
enableHighlight = false;
276
enableCustomDirective = false;
277
278
activeDirectiveRefs: DirectiveRef<unknown>[] = [];
279
280
get activeDirectives(): DynamicDirectiveDef<unknown>[] {
281
const directives: DynamicDirectiveDef<unknown>[] = [];
282
283
if (this.enableTooltip) {
284
directives.push(dynamicDirectiveDef(TooltipDirective, {
285
tooltip: 'Dynamic tooltip content',
286
position: 'top'
287
}, {
288
tooltipShow: () => console.log('Tooltip displayed'),
289
tooltipHide: () => console.log('Tooltip hidden')
290
}));
291
}
292
293
if (this.enableHighlight) {
294
directives.push(dynamicDirectiveDef(HighlightDirective, {
295
highlightColor: this.getHighlightColor(),
296
highlightDuration: 1500
297
}, {
298
highlightStart: () => this.onHighlightStart(),
299
highlightEnd: () => this.onHighlightEnd()
300
}));
301
}
302
303
if (this.enableCustomDirective) {
304
directives.push(dynamicDirectiveDef(CustomBehaviorDirective, {
305
behavior: 'enhanced',
306
sensitivity: 0.8
307
}));
308
}
309
310
return directives;
311
}
312
313
trackDirectives(directiveRefs: DirectiveRef<unknown>[]) {
314
this.activeDirectiveRefs = directiveRefs;
315
console.log(`Active directives updated: ${directiveRefs.length} directives`);
316
317
// Set up cleanup for each directive
318
directiveRefs.forEach(ref => {
319
ref.onDestroy(() => {
320
console.log(`Directive ${ref.type.name} destroyed`);
321
});
322
});
323
}
324
325
private getHighlightColor(): string {
326
const colors = ['yellow', 'lightblue', 'lightgreen', 'pink'];
327
return colors[Math.floor(Math.random() * colors.length)];
328
}
329
330
private onHighlightStart() {
331
console.log('Highlight effect started');
332
}
333
334
private onHighlightEnd() {
335
console.log('Highlight effect completed');
336
}
337
}
338
```
339
340
### Working with NgComponentOutlet
341
342
Apply dynamic directives to components created by NgComponentOutlet:
343
344
```typescript
345
import { ComponentOutletIoDirective, DynamicDirectivesDirective } from 'ng-dynamic-component';
346
347
@Component({
348
standalone: true,
349
imports: [ComponentOutletIoDirective, DynamicDirectivesDirective],
350
template: `
351
<ng-container
352
*ngComponentOutlet="selectedComponent"
353
[ngComponentOutletNdcDynamicDirectives]="outletDirectives"
354
[ngComponentOutletNdcDynamicInputs]="componentInputs"
355
(ndcDynamicDirectivesCreated)="onOutletDirectivesCreated($event)">
356
</ng-container>
357
`
358
})
359
export class OutletDynamicDirectivesComponent {
360
selectedComponent = DashboardComponent;
361
362
componentInputs = {
363
title: 'Dashboard with Dynamic Directives',
364
refreshRate: 5000
365
};
366
367
outletDirectives: DynamicDirectiveDef<unknown>[] = [
368
dynamicDirectiveDef(TooltipDirective, {
369
tooltip: 'This dashboard updates every 5 seconds',
370
position: 'bottom'
371
}),
372
373
dynamicDirectiveDef(HighlightDirective, {
374
highlightColor: 'lightcyan',
375
highlightDuration: 3000
376
}, {
377
highlightStart: () => console.log('Dashboard highlighted'),
378
highlightEnd: () => console.log('Dashboard highlight ended')
379
})
380
];
381
382
onOutletDirectivesCreated(directiveRefs: DirectiveRef<unknown>[]) {
383
console.log('Outlet directives created:', directiveRefs.length);
384
385
// Access the outlet component through directive refs
386
directiveRefs.forEach(ref => {
387
console.log('Host component:', ref.hostComponent);
388
389
// Interact with directive instance
390
if (ref.instance instanceof TooltipDirective) {
391
// Tooltip-specific logic
392
} else if (ref.instance instanceof HighlightDirective) {
393
// Highlight-specific logic
394
}
395
});
396
}
397
}
398
```
399
400
### Advanced Directive Lifecycle Management
401
402
Handle complex directive lifecycle scenarios:
403
404
```typescript
405
@Component({
406
template: `
407
<div class="lifecycle-demo">
408
<button (click)="addDirective()">Add Random Directive</button>
409
<button (click)="removeLastDirective()">Remove Last Directive</button>
410
<button (click)="updateDirectiveInputs()">Update Inputs</button>
411
<button (click)="clearAllDirectives()">Clear All</button>
412
</div>
413
414
<ndc-dynamic
415
[ndcDynamicComponent]="component"
416
[ndcDynamicDirectives]="managedDirectives"
417
(ndcDynamicDirectivesCreated)="handleDirectiveLifecycle($event)">
418
</ndc-dynamic>
419
420
<div class="debug-info">
421
<pre>{{ debugInfo | json }}</pre>
422
</div>
423
`
424
})
425
export class DirectiveLifecycleComponent {
426
component = TestComponent;
427
428
private directiveTypes = [TooltipDirective, HighlightDirective, CustomBehaviorDirective];
429
private directiveCounter = 0;
430
431
managedDirectives: DynamicDirectiveDef<unknown>[] = [];
432
debugInfo: any = {};
433
434
addDirective() {
435
const randomType = this.directiveTypes[Math.floor(Math.random() * this.directiveTypes.length)];
436
const directiveId = ++this.directiveCounter;
437
438
let newDirective: DynamicDirectiveDef<unknown>;
439
440
if (randomType === TooltipDirective) {
441
newDirective = dynamicDirectiveDef(TooltipDirective, {
442
tooltip: `Dynamic tooltip #${directiveId}`,
443
position: ['top', 'bottom', 'left', 'right'][Math.floor(Math.random() * 4)] as any
444
}, {
445
tooltipShow: () => this.logEvent(`Tooltip ${directiveId} shown`),
446
tooltipHide: () => this.logEvent(`Tooltip ${directiveId} hidden`)
447
});
448
} else if (randomType === HighlightDirective) {
449
newDirective = dynamicDirectiveDef(HighlightDirective, {
450
highlightColor: ['yellow', 'lightblue', 'lightgreen'][Math.floor(Math.random() * 3)],
451
highlightDuration: Math.random() * 3000 + 1000
452
}, {
453
highlightStart: () => this.logEvent(`Highlight ${directiveId} started`),
454
highlightEnd: () => this.logEvent(`Highlight ${directiveId} ended`)
455
});
456
} else {
457
newDirective = dynamicDirectiveDef(CustomBehaviorDirective, {
458
behavior: `mode-${directiveId}`,
459
sensitivity: Math.random()
460
});
461
}
462
463
this.managedDirectives = [...this.managedDirectives, newDirective];
464
this.updateDebugInfo();
465
}
466
467
removeLastDirective() {
468
if (this.managedDirectives.length > 0) {
469
this.managedDirectives = this.managedDirectives.slice(0, -1);
470
this.updateDebugInfo();
471
}
472
}
473
474
updateDirectiveInputs() {
475
this.managedDirectives = this.managedDirectives.map(directive => ({
476
...directive,
477
inputs: {
478
...directive.inputs,
479
updatedAt: new Date().toISOString()
480
}
481
}));
482
this.updateDebugInfo();
483
}
484
485
clearAllDirectives() {
486
this.managedDirectives = [];
487
this.updateDebugInfo();
488
}
489
490
handleDirectiveLifecycle(directiveRefs: DirectiveRef<unknown>[]) {
491
console.log('Directive lifecycle event:', directiveRefs.length, 'directives');
492
493
// Track directive creation
494
directiveRefs.forEach((ref, index) => {
495
console.log(`Directive ${index + 1}:`, ref.type.name);
496
497
// Set up destroy callback
498
ref.onDestroy(() => {
499
console.log(`Directive ${ref.type.name} is being destroyed`);
500
});
501
502
// Access directive-specific properties
503
if (ref.instance instanceof TooltipDirective) {
504
// Tooltip directive specific handling
505
console.log('Tooltip text:', (ref.instance as any).tooltip);
506
}
507
});
508
509
this.updateDebugInfo();
510
}
511
512
private logEvent(message: string) {
513
console.log(`[${new Date().toLocaleTimeString()}] ${message}`);
514
}
515
516
private updateDebugInfo() {
517
this.debugInfo = {
518
totalDirectives: this.managedDirectives.length,
519
directiveTypes: this.managedDirectives.map(d => d.type.name),
520
lastUpdate: new Date().toISOString()
521
};
522
}
523
}
524
```
525
526
## Known Limitations
527
528
### OnChanges Hook Limitation
529
530
**⚠️ Important**: Dynamic directives do not trigger the `OnChanges` lifecycle hook. This is a known limitation of the experimental API.
531
532
```typescript
533
// This will NOT work as expected
534
@Directive({
535
selector: '[problematicDirective]'
536
})
537
export class ProblematicDirective implements OnChanges {
538
@Input() value: string = '';
539
540
ngOnChanges(changes: SimpleChanges) {
541
// This method will NOT be called when 'value' input changes
542
// through dynamic directive inputs
543
console.log('Changes:', changes); // Never executed
544
}
545
}
546
547
// Workaround: Use DoCheck instead
548
@Directive({
549
selector: '[workingDirective]'
550
})
551
export class WorkingDirective implements DoCheck {
552
@Input() value: string = '';
553
private previousValue: string = '';
554
555
ngDoCheck() {
556
// Manually detect changes
557
if (this.value !== this.previousValue) {
558
console.log('Value changed:', this.previousValue, '->', this.value);
559
this.previousValue = this.value;
560
}
561
}
562
}
563
```
564
565
## Module Usage
566
567
For NgModule-based applications:
568
569
```typescript
570
import { NgModule } from '@angular/core';
571
import { DynamicDirectivesModule } from 'ng-dynamic-component';
572
573
@NgModule({
574
imports: [
575
DynamicDirectivesModule
576
],
577
// Components can now use dynamic directives
578
})
579
export class MyFeatureModule {}
580
```