Dynamic components with full life-cycle support for inputs and outputs
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Core functionality for creating Angular components dynamically at runtime with full lifecycle support, custom injectors, and projected content.
The main component for dynamically creating other Angular components at runtime.
/**
* Main component for dynamically creating other Angular components
* Supports full component lifecycle, custom injection, and projected content
*/
@Component({
selector: 'ndc-dynamic',
standalone: true,
template: '',
providers: [
{ provide: DynamicComponentInjectorToken, useExisting: DynamicComponent }
]
})
export class DynamicComponent<C = unknown> implements OnChanges, DynamicComponentInjector {
/** The component type to create dynamically */
@Input() ndcDynamicComponent?: Type<C> | null;
/** Custom injector for the dynamic component */
@Input() ndcDynamicInjector?: Injector | null;
/** Additional providers for the dynamic component */
@Input() ndcDynamicProviders?: StaticProvider[] | null;
/** Projected content (projectable nodes) for content projection */
@Input() ndcDynamicContent?: Node[][];
/** NgModule reference for the component (for legacy non-standalone components) */
@Input() ndcDynamicNgModuleRef?: NgModuleRef<unknown>;
/** Environment injector for standalone components */
@Input() ndcDynamicEnvironmentInjector?: EnvironmentInjector | NgModuleRef<unknown>;
/** Emitted when a component is created */
@Output() ndcDynamicCreated: EventEmitter<ComponentRef<C>>;
/** Reference to the currently created component */
componentRef: ComponentRef<C> | null;
/** Manually trigger component creation */
createDynamicComponent(): void;
}Usage Examples:
import { Component, Type, ComponentRef, Injector } from '@angular/core';
import { DynamicComponent } from 'ng-dynamic-component';
// Basic dynamic component creation
@Component({
selector: 'app-basic-example',
template: `
<ndc-dynamic
[ndcDynamicComponent]="selectedComponent"
(ndcDynamicCreated)="onComponentCreated($event)">
</ndc-dynamic>
`
})
export class BasicExampleComponent {
selectedComponent: Type<any> = MyDynamicComponent;
onComponentCreated(componentRef: ComponentRef<any>) {
// Access the component instance
console.log('Component created:', componentRef.instance);
// Set inputs directly on the component
componentRef.instance.title = 'Dynamic Title';
// Trigger change detection
componentRef.changeDetectorRef.detectChanges();
}
}
// Advanced usage with custom injector and providers
@Component({
selector: 'app-advanced-example',
template: `
<ndc-dynamic
[ndcDynamicComponent]="componentType"
[ndcDynamicInjector]="customInjector"
[ndcDynamicProviders]="providers"
[ndcDynamicContent]="projectedContent"
(ndcDynamicCreated)="handleCreated($event)">
</ndc-dynamic>
`
})
export class AdvancedExampleComponent {
componentType = CustomComponent;
customInjector?: Injector;
providers = [
{ provide: 'CONFIG', useValue: { apiUrl: 'https://api.example.com' } },
{ provide: MyService, useClass: MyService }
];
projectedContent?: Node[][];
constructor(private injector: Injector) {
// Create custom injector
this.customInjector = Injector.create({
providers: [
{ provide: 'CUSTOM_TOKEN', useValue: 'custom-value' }
],
parent: this.injector
});
}
handleCreated(componentRef: ComponentRef<CustomComponent>) {
// Component is created with custom injection context
componentRef.instance.initialize();
}
}
// Content projection example
@Component({
selector: 'app-projection-example',
template: `
<ndc-dynamic
[ndcDynamicComponent]="wrapperComponent"
[ndcDynamicContent]="contentNodes">
</ndc-dynamic>
`
})
export class ProjectionExampleComponent implements AfterViewInit {
wrapperComponent = WrapperComponent;
contentNodes?: Node[][];
@ViewChild('content', { read: ElementRef }) contentEl!: ElementRef;
ngAfterViewInit() {
// Create content to project
this.contentNodes = [
[this.contentEl.nativeElement.childNodes[0]]
];
}
}Dynamically switch between different component types:
@Component({
template: `
<button (click)="switchComponent()">Switch Component</button>
<ndc-dynamic
[ndcDynamicComponent]="currentComponent"
(ndcDynamicCreated)="onCreated($event)">
</ndc-dynamic>
`
})
export class ComponentSwitcherComponent {
private components = [ComponentA, ComponentB, ComponentC];
private currentIndex = 0;
get currentComponent() {
return this.components[this.currentIndex];
}
switchComponent() {
this.currentIndex = (this.currentIndex + 1) % this.components.length;
}
onCreated(componentRef: ComponentRef<any>) {
console.log('Switched to:', componentRef.componentType.name);
}
}For standalone components, use the environment injector:
import { EnvironmentInjector } from '@angular/core';
@Component({
template: `
<ndc-dynamic
[ndcDynamicComponent]="standaloneComponent"
[ndcDynamicEnvironmentInjector]="environmentInjector">
</ndc-dynamic>
`
})
export class StandaloneExampleComponent {
standaloneComponent = MyStandaloneComponent;
constructor(public environmentInjector: EnvironmentInjector) {}
}Combine with dynamic input/output directives for complete functionality:
import { DynamicComponent, DynamicIoDirective } from 'ng-dynamic-component';
@Component({
standalone: true,
imports: [DynamicComponent, DynamicIoDirective],
template: `
<ndc-dynamic
[ndcDynamicComponent]="component"
[ndcDynamicInputs]="inputs"
[ndcDynamicOutputs]="outputs"
(ndcDynamicCreated)="onCreated($event)">
</ndc-dynamic>
`
})
export class FullExampleComponent {
component = MyComponent;
inputs = { title: 'Hello', data: [1, 2, 3] };
outputs = {
onSave: (data: any) => console.log('Saved:', data),
onDelete: () => console.log('Deleted')
};
onCreated(componentRef: ComponentRef<any>) {
// Component is created with inputs/outputs bound
}
}/** Interface for objects that can provide ComponentRef access */
interface DynamicComponentInjector {
componentRef: ComponentRef<unknown> | null;
}
/** Injection token for DynamicComponentInjector */
const DynamicComponentInjectorToken: InjectionToken<DynamicComponentInjector>;