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

core-services.mddocs/

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

```