CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ng-dynamic-component

Dynamic components with full life-cycle support for inputs and outputs

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

core-services.mddocs/

Core Services and Dependency Injection

Essential services for component I/O management, reflection utilities, and dependency injection infrastructure that power the ng-dynamic-component library.

Capabilities

Component I/O Management

Abstract service for managing component inputs and outputs with extensible implementation.

/**
 * Abstract service for setting inputs and getting outputs on dynamic components
 * Provides extensible interface for custom I/O management implementations
 */
@Injectable({
  providedIn: 'root',
  useClass: ClassicComponentIO
})
export abstract class ComponentIO {
  /**
   * Sets an input property on a component
   * @param componentRef - Reference to the target component
   * @param name - Name of the input property (must be a valid component input)
   * @param value - Value to set for the input
   */
  abstract setInput<T, K extends ComponentInputKey<T>>(
    componentRef: ComponentRef<T>,
    name: K,
    value: T[K]
  ): void;

  /**
   * Gets an output observable from a component
   * @param componentRef - Reference to the target component
   * @param name - Name of the output property
   * @returns Observable that emits when the output event occurs
   */
  abstract getOutput<T, K extends ComponentInputKey<T>>(
    componentRef: ComponentRef<T>,
    name: K
  ): Observable<unknown>;
}

/**
 * Type constraint for component input property names
 * Ensures type safety when accessing component properties
 */
type ComponentInputKey<T> = keyof T & string;

I/O Factory Service

Factory service for creating and configuring IoService instances with custom options.

/**
 * Factory for creating IoService instances with custom configuration
 * Enables per-component customization of I/O behavior
 */
@Injectable({ providedIn: 'root' })
export class IoFactoryService {
  /**
   * Creates a new IoService instance with specified configuration
   * @param componentInjector - Injector that provides ComponentRef access
   * @param ioOptions - Combined configuration options for the service
   * @returns Configured IoService instance ready for use
   */
  create(
    componentInjector: DynamicComponentInjector,
    ioOptions?: IoServiceOptions & IoFactoryServiceOptions
  ): IoService;
}

/**
 * Additional options specific to IoFactoryService.create method
 */
interface IoFactoryServiceOptions {
  /** Optional custom injector for dependency resolution */
  injector?: Injector;
}

I/O Service Configuration

Configuration service for customizing I/O service behavior.

/**
 * Configuration options for IoService instances
 * Controls various aspects of input/output management behavior
 */
@Injectable({ providedIn: 'root' })
export class IoServiceOptions {
  /** 
   * Whether to track output changes for performance optimization
   * When enabled, prevents unnecessary subscription updates
   */
  trackOutputChanges: boolean = false;
}

/**
 * Core service for managing dynamic inputs and outputs on components
 * Handles property binding, event subscription, and lifecycle management
 */
@Injectable()
export class IoService implements OnDestroy {
  /**
   * Updates component inputs and outputs
   * Applies new input values and manages output event subscriptions
   * @param inputs - Object mapping input names to values (null to clear)
   * @param outputs - Object mapping output names to handlers (null to clear)
   */
  update(inputs?: InputsType | null, outputs?: OutputsType | null): void;
}

Usage Examples:

import { Component, Injectable, Injector } from '@angular/core';
import { 
  IoFactoryService, 
  IoService, 
  IoServiceOptions,
  DynamicComponentInjector,
  ComponentIO 
} from 'ng-dynamic-component';

// Custom I/O service configuration
@Injectable()
export class CustomIoServiceOptions extends IoServiceOptions {
  trackOutputChanges = true; // Enable output change tracking
  
  // Add custom configuration properties
  debugMode = false;
  logInputChanges = true;
}

// Using IoFactoryService for custom I/O management
@Component({
  template: `
    <ndc-dynamic 
      [ndcDynamicComponent]="component"
      #dynamicComponent="ndcDynamicIo">
    </ndc-dynamic>
    
    <button (click)="createCustomIoService(dynamicComponent)">
      Create Custom I/O Service
    </button>
  `
})
export class CustomIoServiceComponent {
  component = MyComponent;

  constructor(
    private ioFactory: IoFactoryService,
    private injector: Injector
  ) {}

  createCustomIoService(componentInjector: DynamicComponentInjector) {
    // Create custom I/O service with specific configuration
    const customIoService = this.ioFactory.create(componentInjector, {
      trackOutputChanges: true,
      injector: this.injector
    });

    // Use the custom service
    customIoService.update(
      { title: 'Custom I/O Service', data: [1, 2, 3] },
      { onSave: (data: any) => console.log('Custom save:', data) }
    );
  }
}

// Advanced I/O service usage with lifecycle management
@Component({
  template: `
    <div class="io-controls">
      <button (click)="startManualIoManagement()">Start Manual I/O</button>
      <button (click)="stopManualIoManagement()">Stop Manual I/O</button>
      <button (click)="updateInputs()">Update Inputs</button>
    </div>
    
    <ndc-dynamic 
      [ndcDynamicComponent]="component"
      #componentRef="ndcComponentOutletInjector">
    </ndc-dynamic>
  `
})
export class ManualIoManagementComponent implements OnDestroy {
  component = DataComponent;
  
  private manualIoService?: IoService;
  private inputUpdateInterval?: number;

  constructor(private ioFactory: IoFactoryService) {}

  startManualIoManagement() {
    if (this.manualIoService) {
      return; // Already started
    }

    // Create I/O service with custom options
    this.manualIoService = this.ioFactory.create(
      { componentRef: this.getComponentRef() },
      { 
        trackOutputChanges: true,
        injector: this.getCustomInjector()
      }
    );

    // Set up initial I/O
    this.manualIoService.update(
      { title: 'Manual Management', refreshRate: 1000 },
      { 
        onDataChange: (data: any) => this.handleDataChange(data),
        onError: (error: any) => this.handleError(error)
      }
    );

    // Set up periodic input updates
    this.inputUpdateInterval = window.setInterval(() => {
      this.updateInputs();
    }, 2000);

    console.log('Manual I/O management started');
  }

  stopManualIoManagement() {
    if (this.manualIoService) {
      // Clear inputs and outputs
      this.manualIoService.update(null, null);
      this.manualIoService = undefined;
    }

    if (this.inputUpdateInterval) {
      clearInterval(this.inputUpdateInterval);
      this.inputUpdateInterval = undefined;
    }

    console.log('Manual I/O management stopped');
  }

  updateInputs() {
    if (this.manualIoService) {
      this.manualIoService.update({
        title: `Updated at ${new Date().toLocaleTimeString()}`,
        data: Array.from({ length: 5 }, () => Math.floor(Math.random() * 100)),
        timestamp: Date.now()
      });
    }
  }

  ngOnDestroy() {
    this.stopManualIoManagement();
  }

  private getComponentRef() {
    // Implementation to get component reference
    return null; // Placeholder
  }

  private getCustomInjector() {
    // Create custom injector with additional providers
    return Injector.create({
      providers: [
        { provide: 'CUSTOM_CONFIG', useValue: { apiUrl: '/api' } }
      ]
    });
  }

  private handleDataChange(data: any) {
    console.log('Data changed:', data);
  }

  private handleError(error: any) {
    console.error('Component error:', error);
  }
}

Reflection Services

Services for accessing component metadata and reflection information.

/**
 * Contract for Reflect API subsystem required by the library
 * Abstracts the reflection capabilities needed for metadata access
 */
interface ReflectApi {
  /**
   * Gets metadata from an object using reflection
   * @param type - The metadata type key to retrieve
   * @param obj - The object to get metadata from
   * @returns Array of metadata values
   */
  getMetadata(type: string, obj: unknown): unknown[];
}

/**
 * Injection token for ReflectApi implementation
 * Provides access to reflection capabilities
 * Default factory returns window.Reflect
 */
const ReflectRef: InjectionToken<ReflectApi>;

/**
 * Service for accessing reflection metadata on classes and objects
 * Used internally for component analysis and dependency injection
 */
@Injectable({ providedIn: 'root' })
export class ReflectService {
  /**
   * Gets constructor parameter types using reflection
   * @param ctor - Constructor function to analyze
   * @returns Array of parameter types
   */
  getCtorParamTypes(ctor: Type<unknown>): unknown[];
}

Usage Examples:

import { Injectable, Type } from '@angular/core';
import { ReflectService, ReflectApi, ReflectRef } from 'ng-dynamic-component';

// Custom reflection implementation
@Injectable()
export class CustomReflectApi implements ReflectApi {
  getMetadata(type: string, obj: unknown): unknown[] {
    // Custom metadata retrieval logic
    if (typeof obj === 'function' && (obj as any).__metadata__) {
      return (obj as any).__metadata__[type] || [];
    }
    return [];
  }
}

// Using reflection service for component analysis
@Injectable({ providedIn: 'root' })
export class ComponentAnalyzer {
  constructor(private reflectService: ReflectService) {}

  analyzeComponent<T>(componentType: Type<T>): ComponentMetadata {
    // Get constructor parameter types
    const paramTypes = this.reflectService.getCtorParamTypes(componentType);
    
    console.log('Component constructor parameters:', paramTypes);
    
    return {
      name: componentType.name,
      parameterTypes: paramTypes,
      hasCustomInjection: paramTypes.length > 0
    };
  }
}

interface ComponentMetadata {
  name: string;
  parameterTypes: unknown[];
  hasCustomInjection: boolean;
}

// Providing custom reflection implementation
@Component({
  providers: [
    { provide: ReflectRef, useClass: CustomReflectApi }
  ],
  template: `<!-- Component with custom reflection -->`
})
export class CustomReflectionComponent {
  constructor(private componentAnalyzer: ComponentAnalyzer) {
    // Analyze various component types
    this.analyzeComponents();
  }

  private analyzeComponents() {
    const componentsToAnalyze = [
      MyComponent,
      AnotherComponent,
      ComplexComponent
    ];

    componentsToAnalyze.forEach(component => {
      const metadata = this.componentAnalyzer.analyzeComponent(component);
      console.log('Component analysis:', metadata);
    });
  }
}

Dependency Injection Infrastructure

Core interfaces and tokens for dependency injection throughout the library.

/**
 * Interface for objects that can provide access to ComponentRef instances
 * Core abstraction for component reference access across the library
 */
interface DynamicComponentInjector {
  /** Reference to the dynamic component instance (null if not created) */
  componentRef: ComponentRef<unknown> | null;
}

/**
 * Angular DI token for injecting DynamicComponentInjector instances
 * Used throughout the library for accessing component references
 */
const DynamicComponentInjectorToken: InjectionToken<DynamicComponentInjector>;

Usage Examples:

import { Component, Inject, Injectable } from '@angular/core';
import { DynamicComponentInjector, DynamicComponentInjectorToken } from 'ng-dynamic-component';

// Service that works with dynamic components
@Injectable()
export class DynamicComponentService {
  constructor(
    @Inject(DynamicComponentInjectorToken) 
    private componentInjector: DynamicComponentInjector
  ) {}

  getComponentInstance<T>(): T | null {
    const ref = this.componentInjector.componentRef;
    return ref ? (ref.instance as T) : null;
  }

  executeOnComponent<T>(action: (instance: T) => void): boolean {
    const instance = this.getComponentInstance<T>();
    if (instance) {
      action(instance);
      return true;
    }
    return false;
  }

  triggerComponentMethod(methodName: string, ...args: any[]): any {
    const instance = this.getComponentInstance();
    if (instance && typeof (instance as any)[methodName] === 'function') {
      return (instance as any)[methodName](...args);
    }
    return null;
  }
}

// Custom component injector implementation
@Injectable()
export class CustomComponentInjector implements DynamicComponentInjector {
  private _componentRef: ComponentRef<unknown> | null = null;

  get componentRef(): ComponentRef<unknown> | null {
    return this._componentRef;
  }

  setComponentRef(ref: ComponentRef<unknown> | null) {
    this._componentRef = ref;
  }
}

// Component using dependency injection infrastructure
@Component({
  providers: [
    DynamicComponentService,
    { provide: DynamicComponentInjectorToken, useClass: CustomComponentInjector }
  ],
  template: `
    <div class="component-controls">
      <button (click)="executeAction()">Execute Action</button>
      <button (click)="callMethod()">Call Method</button>
      <button (click)="getInfo()">Get Component Info</button>
    </div>
    
    <ndc-dynamic 
      [ndcDynamicComponent]="component"
      (ndcDynamicCreated)="onComponentCreated($event)">
    </ndc-dynamic>
  `
})
export class DependencyInjectionExampleComponent {
  component = InteractiveComponent;

  constructor(
    private dynamicService: DynamicComponentService,
    @Inject(DynamicComponentInjectorToken) 
    private componentInjector: DynamicComponentInjector
  ) {}

  onComponentCreated(componentRef: ComponentRef<any>) {
    // Update the injector with the new component reference
    if (this.componentInjector instanceof CustomComponentInjector) {
      this.componentInjector.setComponentRef(componentRef);
    }
  }

  executeAction() {
    this.dynamicService.executeOnComponent<any>(instance => {
      console.log('Executing action on:', instance.constructor.name);
      if (instance.performAction) {
        instance.performAction('custom-action');
      }
    });
  }

  callMethod() {
    const result = this.dynamicService.triggerComponentMethod('getData');
    console.log('Method result:', result);
  }

  getInfo() {
    const instance = this.dynamicService.getComponentInstance();
    console.log('Component instance:', instance);
    console.log('Component type:', instance?.constructor.name);
  }
}

Advanced Service Integration

Complex scenarios involving multiple services and custom configurations.

// Multi-service integration with custom configuration
@Injectable({ providedIn: 'root' })
export class AdvancedDynamicComponentManager {
  constructor(
    private ioFactory: IoFactoryService,
    private reflectService: ReflectService,
    private componentIO: ComponentIO
  ) {}

  createManagedComponent<T>(
    componentType: Type<T>,
    container: ViewContainerRef,
    config: ManagedComponentConfig<T>
  ): ManagedComponentRef<T> {
    // Analyze component using reflection
    const metadata = this.analyzeComponent(componentType);
    
    // Create component with custom injector
    const componentRef = container.createComponent(componentType, {
      injector: config.injector
    });

    // Create custom I/O service
    const ioService = this.ioFactory.create(
      { componentRef },
      config.ioOptions
    );

    // Set up inputs and outputs
    if (config.inputs) {
      ioService.update(config.inputs, null);
    }
    if (config.outputs) {
      ioService.update(null, config.outputs);
    }

    return new ManagedComponentRef(componentRef, ioService, metadata);
  }

  private analyzeComponent<T>(componentType: Type<T>) {
    return {
      parameterTypes: this.reflectService.getCtorParamTypes(componentType),
      name: componentType.name
    };
  }
}

interface ManagedComponentConfig<T> {
  injector?: Injector;
  inputs?: InputsType;
  outputs?: OutputsType;
  ioOptions?: IoServiceOptions;
}

class ManagedComponentRef<T> {
  constructor(
    public componentRef: ComponentRef<T>,
    public ioService: IoService,
    public metadata: any
  ) {}

  updateIO(inputs?: InputsType, outputs?: OutputsType) {
    this.ioService.update(inputs, outputs);
  }

  destroy() {
    this.ioService.update(null, null); // Clear I/O
    this.componentRef.destroy();
  }
}

Event Context System

Tokens for managing custom contexts in output event handlers.

/**
 * Factory function for the default event argument token value
 * @returns The string '$event' used as the default event argument placeholder
 */
function defaultEventArgumentFactory(): string;

/**
 * Injection token for customizing the event argument placeholder string
 * Default value is '$event', can be overridden for custom event handling
 */
const IoEventArgumentToken: InjectionToken<string>;

/**
 * @deprecated Since v10.4.0 - Use IoEventArgumentToken instead
 */
const EventArgumentToken: InjectionToken<string>;

/**
 * Injection token for providing custom context objects to output handlers
 * Allows output functions to be bound to specific context objects
 */
const IoEventContextToken: InjectionToken<unknown>;

/**
 * Injection token for providing custom context provider configuration
 * Used to configure how IoEventContextToken should be resolved
 */
const IoEventContextProviderToken: InjectionToken<StaticProvider>;

Usage Examples:

import { Component, Injectable, InjectionToken } from '@angular/core';
import { 
  IoEventContextToken, 
  IoEventContextProviderToken,
  IoEventArgumentToken,
  OutputsType 
} from 'ng-dynamic-component';

// Custom event context implementation
@Injectable()
export class CustomEventContext {
  private apiService = inject(ApiService);
  private logger = inject(LoggerService);

  async handleSaveEvent(data: any, eventInfo: any) {
    this.logger.log('Save event triggered:', { data, eventInfo });
    
    try {
      const result = await this.apiService.save(data);
      this.logger.log('Save successful:', result);
      return result;
    } catch (error) {
      this.logger.error('Save failed:', error);
      throw error;
    }
  }

  handleDeleteEvent(id: string) {
    return this.apiService.delete(id);
  }
}

// Component using custom event context
@Component({
  providers: [
    CustomEventContext,
    {
      provide: IoEventContextProviderToken,
      useValue: { provide: IoEventContextToken, useClass: CustomEventContext }
    }
  ],
  template: `
    <ndc-dynamic 
      [ndcDynamicComponent]="component"
      [ndcDynamicInputs]="inputs"
      [ndcDynamicOutputs]="outputs">
    </ndc-dynamic>
  `
})
export class EventContextExampleComponent {
  component = DataFormComponent;
  
  inputs = {
    title: 'Event Context Demo',
    data: { name: 'John', email: 'john@example.com' }
  };

  // Output handlers will be bound to CustomEventContext instance
  outputs: OutputsType = {
    // Handler method will be called with CustomEventContext as 'this'
    onSave: function(this: CustomEventContext, data: any) {
      return this.handleSaveEvent(data, { timestamp: Date.now() });
    },
    
    onDelete: function(this: CustomEventContext, id: string) {
      return this.handleDeleteEvent(id);
    },

    // Event argument customization
    onValidate: {
      handler: function(this: CustomEventContext, formData: any, customArg: string) {
        console.log('Validation with custom context:', { formData, customArg });
        return formData && Object.keys(formData).length > 0;
      },
      args: ['$event', 'validation-context'] // '$event' will be replaced with actual event
    }
  };
}

// Global event context configuration
@Component({
  providers: [
    // Custom event argument placeholder
    { provide: IoEventArgumentToken, useValue: '$data' },
    
    // Global event context
    { provide: IoEventContextToken, useClass: GlobalEventContext }
  ],
  template: `
    <div class="global-context-demo">
      <ndc-dynamic 
        [ndcDynamicComponent]="component"
        [ndcDynamicOutputs]="globalOutputs">
      </ndc-dynamic>
    </div>
  `
})
export class GlobalEventContextComponent {
  component = NotificationComponent;

  globalOutputs: OutputsType = {
    // Using custom event argument placeholder '$data'
    onNotify: {
      handler: (message: string, level: string) => {
        console.log(`[${level.toUpperCase()}] ${message}`);
      },
      args: ['$data', 'info'] // '$data' matches IoEventArgumentToken value
    },

    // Direct function (will be bound to GlobalEventContext)
    onDismiss: function(this: GlobalEventContext) {
      this.handleDismiss();
    }
  };
}

@Injectable()
export class GlobalEventContext {
  handleDismiss() {
    console.log('Notification dismissed via global context');
  }
}

Module Exports

NgModule classes for organizing and importing library functionality.

/**
 * Main module that includes all dynamic component functionality
 * Exports DynamicComponent and DynamicIoModule
 */
@NgModule({
  imports: [DynamicIoModule, DynamicComponent],
  exports: [DynamicIoModule, DynamicComponent]
})
export class DynamicModule {}

/**
 * Module for dynamic I/O directives and related functionality
 * Includes ComponentOutletInjectorModule
 */
@NgModule({
  imports: [DynamicIoDirective],
  exports: [DynamicIoDirective, ComponentOutletInjectorModule]
})
export class DynamicIoModule {}

/**
 * Module for dynamic attributes directive
 * Includes ComponentOutletInjectorModule for NgComponentOutlet support
 */
@NgModule({
  imports: [DynamicAttributesDirective],
  exports: [DynamicAttributesDirective, ComponentOutletInjectorModule]
})
export class DynamicAttributesModule {}

/**
 * Module for experimental dynamic directives functionality
 * Includes ComponentOutletInjectorModule
 */
@NgModule({
  imports: [DynamicDirectivesDirective],
  exports: [DynamicDirectivesDirective, ComponentOutletInjectorModule]
})
export class DynamicDirectivesModule {}

/**
 * Module for NgComponentOutlet enhancement directives
 * Provides component reference access and I/O capabilities
 */
@NgModule({
  imports: [ComponentOutletInjectorDirective, ComponentOutletIoDirective],
  exports: [ComponentOutletInjectorDirective, ComponentOutletIoDirective]
})
export class ComponentOutletInjectorModule {}

docs

component-outlet.md

core-services.md

dynamic-attributes.md

dynamic-component.md

dynamic-directives.md

index.md

input-output.md

tile.json