or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

component-outlet.mdcore-services.mddynamic-attributes.mddynamic-component.mddynamic-directives.mdindex.mdinput-output.md

dynamic-directives.mddocs/

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

```