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
Directives that extend Angular's built-in NgComponentOutlet with dynamic input/output binding, component reference access, and additional lifecycle management capabilities.
Provides access to the ComponentRef created by NgComponentOutlet for further manipulation and lifecycle management.
/**
* Provides ComponentRef access for NgComponentOutlet
* Enables accessing the component instance created by the outlet
*/
@Directive({
selector: '[ngComponentOutlet]',
standalone: true,
exportAs: 'ndcComponentOutletInjector'
})
export class ComponentOutletInjectorDirective {
/**
* Reference to the component created by NgComponentOutlet
* Provides access to the component instance and lifecycle methods
*/
readonly componentRef: ComponentRef<unknown>;
}Enables dynamic input/output binding for components created by NgComponentOutlet.
/**
* Enables dynamic inputs/outputs for NgComponentOutlet
* Works in conjunction with Angular's built-in outlet directive
*/
@Directive({
selector: '[ngComponentOutletNdcDynamicInputs],[ngComponentOutletNdcDynamicOutputs]',
standalone: true,
exportAs: 'ndcDynamicIo'
})
export class ComponentOutletIoDirective implements DoCheck {
/** Dynamic inputs for the outlet component */
@Input() ngComponentOutletNdcDynamicInputs?: InputsType | null;
/** Dynamic outputs for the outlet component */
@Input() ngComponentOutletNdcDynamicOutputs?: OutputsType | null;
}NgModule that provides both outlet enhancement directives for traditional module-based applications.
/**
* Module that exports both outlet enhancement directives
* Use this in NgModule-based applications
*/
@NgModule({
imports: [ComponentOutletInjectorDirective, ComponentOutletIoDirective],
exports: [ComponentOutletInjectorDirective, ComponentOutletIoDirective]
})
export class ComponentOutletInjectorModule {}Usage Examples:
import { Component, Type, ComponentRef } from '@angular/core';
import { ComponentOutletInjectorDirective, ComponentOutletIoDirective } from 'ng-dynamic-component';
// Basic outlet with component reference access
@Component({
standalone: true,
imports: [ComponentOutletInjectorDirective],
template: `
<ng-container
*ngComponentOutlet="selectedComponent"
#outlet="ndcComponentOutletInjector">
</ng-container>
<button (click)="accessComponent(outlet.componentRef)">
Access Component
</button>
`
})
export class OutletBasicExampleComponent {
selectedComponent: Type<any> = MyComponent;
accessComponent(componentRef: ComponentRef<any>) {
console.log('Component instance:', componentRef.instance);
// Directly modify component properties
componentRef.instance.title = 'Modified Title';
// Trigger change detection
componentRef.changeDetectorRef.detectChanges();
// Access component methods
if (typeof componentRef.instance.refresh === 'function') {
componentRef.instance.refresh();
}
}
}
// Outlet with dynamic I/O binding
@Component({
standalone: true,
imports: [ComponentOutletIoDirective],
template: `
<ng-container
*ngComponentOutlet="componentType"
[ngComponentOutletNdcDynamicInputs]="inputs"
[ngComponentOutletNdcDynamicOutputs]="outputs">
</ng-container>
`
})
export class OutletIoExampleComponent {
componentType = UserProfileComponent;
inputs = {
userId: 123,
editMode: false,
theme: 'light'
};
outputs = {
onSave: (userData: any) => this.saveUser(userData),
onCancel: () => this.cancelEdit(),
onProfilePictureChange: (imageUrl: string) => this.updateProfilePicture(imageUrl)
};
saveUser(userData: any) {
console.log('Saving user:', userData);
// Implement save logic
}
cancelEdit() {
console.log('Edit cancelled');
this.inputs = { ...this.inputs, editMode: false };
}
updateProfilePicture(imageUrl: string) {
console.log('Profile picture updated:', imageUrl);
// Handle image update
}
}
// Combined usage with both directives
@Component({
standalone: true,
imports: [ComponentOutletInjectorDirective, ComponentOutletIoDirective],
template: `
<ng-container
*ngComponentOutlet="currentComponent"
[ngComponentOutletNdcDynamicInputs]="dynamicInputs"
[ngComponentOutletNdcDynamicOutputs]="dynamicOutputs"
#outlet="ndcComponentOutletInjector">
</ng-container>
<div class="controls">
<button (click)="refreshComponent(outlet.componentRef)">Refresh</button>
<button (click)="switchComponent()">Switch Component</button>
</div>
`
})
export class OutletCombinedExampleComponent {
private components = [DashboardComponent, SettingsComponent, ProfileComponent];
private currentIndex = 0;
get currentComponent() {
return this.components[this.currentIndex];
}
get dynamicInputs() {
// Return different inputs based on current component
switch (this.currentComponent) {
case DashboardComponent:
return { userId: 123, refreshInterval: 5000 };
case SettingsComponent:
return { theme: 'dark', notifications: true };
case ProfileComponent:
return { userId: 123, editMode: false };
default:
return {};
}
}
get dynamicOutputs() {
return {
onAction: (action: string, data?: any) => this.handleAction(action, data),
onNavigate: (route: string) => this.navigate(route)
};
}
refreshComponent(componentRef: ComponentRef<any>) {
if (typeof componentRef.instance.refresh === 'function') {
componentRef.instance.refresh();
}
}
switchComponent() {
this.currentIndex = (this.currentIndex + 1) % this.components.length;
console.log('Switched to:', this.currentComponent.name);
}
handleAction(action: string, data?: any) {
console.log('Action received:', action, data);
}
navigate(route: string) {
console.log('Navigate to:', route);
// Implement navigation logic
}
}Handle nested components and complex injection scenarios:
@Component({
template: `
<div class="parent-container">
<ng-container
*ngComponentOutlet="
parentComponent;
injector: customInjector;
content: projectedNodes
"
[ngComponentOutletNdcDynamicInputs]="parentInputs"
[ngComponentOutletNdcDynamicOutputs]="parentOutputs"
#parentOutlet="ndcComponentOutletInjector">
</ng-container>
<!-- Child component that depends on parent -->
<ng-container
*ngComponentOutlet="childComponent"
[ngComponentOutletNdcDynamicInputs]="getChildInputs(parentOutlet.componentRef)"
[ngComponentOutletNdcDynamicOutputs]="childOutputs">
</ng-container>
</div>
`
})
export class HierarchicalOutletComponent {
parentComponent = ParentComponent;
childComponent = ChildComponent;
parentInputs = {
title: 'Parent Component',
configuration: { mode: 'advanced' }
};
parentOutputs = {
onConfigChange: (config: any) => this.updateConfiguration(config)
};
childOutputs = {
onChildEvent: (event: any) => this.handleChildEvent(event)
};
constructor(private injector: Injector) {}
get customInjector() {
return Injector.create({
providers: [
{ provide: 'PARENT_CONFIG', useValue: { theme: 'blue' } }
],
parent: this.injector
});
}
get projectedNodes() {
// Create content to project into parent component
return [
// Projected content nodes
];
}
getChildInputs(parentComponentRef: ComponentRef<any>) {
// Child inputs depend on parent component state
return {
parentData: parentComponentRef?.instance?.data || null,
inherited: true
};
}
updateConfiguration(config: any) {
console.log('Parent configuration updated:', config);
}
handleChildEvent(event: any) {
console.log('Child event received:', event);
}
}Use outlet components with router data and parameters:
import { ActivatedRoute } from '@angular/router';
@Component({
template: `
<ng-container
*ngComponentOutlet="getComponentForRoute()"
[ngComponentOutletNdcDynamicInputs]="routeInputs"
[ngComponentOutletNdcDynamicOutputs]="routeOutputs">
</ng-container>
`
})
export class RoutedOutletComponent implements OnInit {
private routeData: any = {};
private routeParams: any = {};
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// Subscribe to route data and parameters
this.route.data.subscribe(data => {
this.routeData = data;
});
this.route.params.subscribe(params => {
this.routeParams = params;
});
}
getComponentForRoute(): Type<any> {
// Select component based on route data
return this.routeData.component || DefaultComponent;
}
get routeInputs() {
return {
...this.routeParams,
routeData: this.routeData,
timestamp: Date.now()
};
}
get routeOutputs() {
return {
onRouteAction: (action: string) => this.handleRouteAction(action)
};
}
handleRouteAction(action: string) {
console.log('Route action:', action);
// Handle route-specific actions
}
}For NgModule-based applications:
import { NgModule } from '@angular/core';
import { ComponentOutletInjectorModule } from 'ng-dynamic-component';
@NgModule({
imports: [
ComponentOutletInjectorModule
],
// Component can now use outlet directives
})
export class MyFeatureModule {}