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
System for dynamically setting and managing HTML attributes on component elements at runtime, supporting both declarative attribute binding and imperative attribute manipulation.
Main directive for dynamically setting HTML attributes on component host elements.
/**
* Dynamically sets HTML attributes on component elements
* Automatically updates attributes when the attribute map changes
*/
@Directive({
selector: '[ndcDynamicAttributes],[ngComponentOutletNdcDynamicAttributes]',
standalone: true,
exportAs: 'ndcDynamicAttributes'
})
export class DynamicAttributesDirective implements DoCheck {
/** Attributes for ndc-dynamic components */
@Input() ndcDynamicAttributes?: AttributesMap | null;
/** Attributes for NgComponentOutlet components */
@Input() ngComponentOutletNdcDynamicAttributes?: AttributesMap | null;
/**
* Manually set an attribute on the host element
* @param name - Attribute name
* @param value - Attribute value
* @param namespace - Optional namespace for the attribute
*/
setAttribute(name: string, value: string, namespace?: string): void;
/**
* Manually remove an attribute from the host element
* @param name - Attribute name to remove
* @param namespace - Optional namespace for the attribute
*/
removeAttribute(name: string, namespace?: string): void;
}NgModule that provides dynamic attributes functionality for traditional module-based applications.
/**
* Module for dynamic attributes functionality
* Includes component outlet integration
*/
@NgModule({
imports: [DynamicAttributesDirective, ComponentOutletInjectorModule],
exports: [DynamicAttributesDirective, ComponentOutletInjectorModule]
})
export class DynamicAttributesModule {}/**
* Map of attribute names to their string values
* Used for declarative attribute binding
*/
interface AttributesMap {
[key: string]: string;
}Usage Examples:
import { Component } from '@angular/core';
import { DynamicComponent, DynamicAttributesDirective, AttributesMap } from 'ng-dynamic-component';
// Basic dynamic attributes with ndc-dynamic
@Component({
standalone: true,
imports: [DynamicComponent, DynamicAttributesDirective],
template: `
<ndc-dynamic
[ndcDynamicComponent]="component"
[ndcDynamicAttributes]="attributes">
</ndc-dynamic>
`
})
export class BasicAttributesExampleComponent {
component = MyComponent;
attributes: AttributesMap = {
'data-testid': 'dynamic-component',
'aria-label': 'Dynamic content area',
'role': 'region',
'class': 'theme-dark border-rounded',
'style': 'min-height: 200px; background: #f5f5f5;'
};
}
// Dynamic attribute changes
@Component({
template: `
<div class="controls">
<button (click)="toggleTheme()">Toggle Theme</button>
<button (click)="updateAccessibility()">Update Accessibility</button>
<button (click)="addCustomAttributes()">Add Custom Attrs</button>
</div>
<ndc-dynamic
[ndcDynamicComponent]="component"
[ndcDynamicAttributes]="currentAttributes"
#dynamicAttrs="ndcDynamicAttributes">
</ndc-dynamic>
<button (click)="imperativeUpdate(dynamicAttrs)">Imperative Update</button>
`
})
export class DynamicAttributesExampleComponent {
component = ContentComponent;
private theme: 'light' | 'dark' = 'light';
private accessibilityLevel: 'basic' | 'enhanced' = 'basic';
private customAttrsEnabled = false;
get currentAttributes(): AttributesMap {
const attrs: AttributesMap = {
'data-theme': this.theme,
'class': `theme-${this.theme} component-wrapper`,
'role': 'main'
};
// Add accessibility attributes
if (this.accessibilityLevel === 'enhanced') {
attrs['aria-live'] = 'polite';
attrs['aria-describedby'] = 'component-description';
attrs['tabindex'] = '0';
}
// Add custom attributes
if (this.customAttrsEnabled) {
attrs['data-custom'] = 'enabled';
attrs['data-timestamp'] = Date.now().toString();
attrs['data-version'] = '1.0.0';
}
return attrs;
}
toggleTheme() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
}
updateAccessibility() {
this.accessibilityLevel = this.accessibilityLevel === 'basic' ? 'enhanced' : 'basic';
}
addCustomAttributes() {
this.customAttrsEnabled = !this.customAttrsEnabled;
}
imperativeUpdate(directiveRef: DynamicAttributesDirective) {
// Manually set attributes using directive methods
directiveRef.setAttribute('data-manual', 'true');
directiveRef.setAttribute('data-updated', new Date().toISOString());
// Set namespaced attribute
directiveRef.setAttribute('custom-attr', 'value', 'custom-namespace');
// Remove an attribute
setTimeout(() => {
directiveRef.removeAttribute('data-manual');
}, 3000);
}
}Use dynamic attributes with Angular's built-in component outlet:
import { ComponentOutletIoDirective, DynamicAttributesDirective } from 'ng-dynamic-component';
@Component({
standalone: true,
imports: [ComponentOutletIoDirective, DynamicAttributesDirective],
template: `
<ng-container
*ngComponentOutlet="selectedComponent"
[ngComponentOutletNdcDynamicAttributes]="outletAttributes"
[ngComponentOutletNdcDynamicInputs]="inputs">
</ng-container>
`
})
export class OutletAttributesExampleComponent {
selectedComponent = DashboardComponent;
inputs = {
title: 'Dashboard',
refreshRate: 5000
};
outletAttributes: AttributesMap = {
'data-component': 'dashboard',
'aria-label': 'Dashboard content',
'class': 'outlet-component dashboard-theme',
'data-refresh-rate': '5000'
};
}Create dynamic attributes based on component state and conditions:
@Component({
template: `
<div class="status-indicators">
<span>Loading: {{ isLoading }}</span>
<span>Error: {{ hasError }}</span>
<span>Theme: {{ currentTheme }}</span>
</div>
<ndc-dynamic
[ndcDynamicComponent]="component"
[ndcDynamicAttributes]="computedAttributes">
</ndc-dynamic>
`
})
export class ConditionalAttributesComponent {
component = DataComponent;
isLoading = false;
hasError = false;
currentTheme = 'light';
userRole = 'user';
featureFlags = ['feature-a', 'feature-b'];
get computedAttributes(): AttributesMap {
const attrs: AttributesMap = {};
// Base attributes
attrs['data-component'] = 'data-display';
attrs['data-theme'] = this.currentTheme;
// Conditional attributes based on state
if (this.isLoading) {
attrs['aria-busy'] = 'true';
attrs['data-loading'] = 'true';
attrs['class'] = 'component-loading';
}
if (this.hasError) {
attrs['aria-invalid'] = 'true';
attrs['data-error'] = 'true';
attrs['class'] = (attrs['class'] || '') + ' component-error';
}
// Role-based attributes
if (this.userRole === 'admin') {
attrs['data-admin'] = 'true';
attrs['data-permissions'] = 'all';
}
// Feature flag attributes
if (this.featureFlags.includes('feature-a')) {
attrs['data-feature-a'] = 'enabled';
}
// Computed styles
attrs['style'] = this.getComputedStyles();
return attrs;
}
private getComputedStyles(): string {
const styles: string[] = [];
if (this.isLoading) {
styles.push('opacity: 0.6');
styles.push('pointer-events: none');
}
if (this.hasError) {
styles.push('border: 2px solid red');
}
if (this.currentTheme === 'dark') {
styles.push('background: #2d2d2d');
styles.push('color: #ffffff');
}
return styles.join('; ');
}
simulateLoading() {
this.isLoading = true;
setTimeout(() => {
this.isLoading = false;
this.hasError = Math.random() > 0.7; // 30% chance of error
}, 2000);
}
}Dynamically manage accessibility attributes for better user experience:
@Component({
template: `
<div class="accessibility-controls">
<label>
<input type="checkbox" [(ngModel)]="screenReaderOptimized" />
Screen Reader Optimized
</label>
<label>
<input type="checkbox" [(ngModel)]="highContrast" />
High Contrast
</label>
<label>
<input type="range" min="1" max="5" [(ngModel)]="complexityLevel" />
Complexity Level: {{ complexityLevel }}
</label>
</div>
<ndc-dynamic
[ndcDynamicComponent]="component"
[ndcDynamicAttributes]="accessibilityAttributes">
</ndc-dynamic>
`
})
export class AccessibilityAttributesComponent {
component = InteractiveComponent;
screenReaderOptimized = false;
highContrast = false;
complexityLevel = 3;
get accessibilityAttributes(): AttributesMap {
const attrs: AttributesMap = {
'role': 'application',
'aria-label': 'Interactive content area'
};
if (this.screenReaderOptimized) {
attrs['aria-live'] = 'polite';
attrs['aria-atomic'] = 'true';
attrs['aria-relevant'] = 'additions text';
attrs['aria-describedby'] = 'sr-instructions';
}
if (this.highContrast) {
attrs['data-high-contrast'] = 'true';
attrs['class'] = 'high-contrast-mode';
}
// Complexity-based attributes
attrs['data-complexity'] = this.complexityLevel.toString();
if (this.complexityLevel <= 2) {
attrs['aria-label'] = 'Simple interactive content';
attrs['data-ui-mode'] = 'simplified';
} else if (this.complexityLevel >= 4) {
attrs['aria-label'] = 'Advanced interactive content with multiple features';
attrs['data-ui-mode'] = 'advanced';
attrs['aria-expanded'] = 'false';
}
return attrs;
}
}Use dynamic attributes for testing identifiers and analytics tracking:
@Component({
template: `
<ndc-dynamic
[ndcDynamicComponent]="component"
[ndcDynamicAttributes]="testingAndAnalyticsAttributes">
</ndc-dynamic>
`
})
export class TestingAttributesComponent implements OnInit {
component = TestableComponent;
private sessionId = '';
private userId = '';
private experimentVariant = '';
ngOnInit() {
this.sessionId = this.generateSessionId();
this.userId = this.getCurrentUserId();
this.experimentVariant = this.getExperimentVariant();
}
get testingAndAnalyticsAttributes(): AttributesMap {
const attrs: AttributesMap = {
// Testing attributes
'data-testid': 'dynamic-component',
'data-qa': 'main-content',
'data-component-type': this.component.name,
// Analytics attributes
'data-analytics-id': 'dynamic-content',
'data-session-id': this.sessionId,
'data-user-id': this.userId,
'data-page-section': 'main',
// A/B testing attributes
'data-experiment': 'layout-test',
'data-variant': this.experimentVariant,
// Performance tracking
'data-track-performance': 'true',
'data-component-load-time': Date.now().toString()
};
return attrs;
}
private generateSessionId(): string {
return 'session-' + Math.random().toString(36).substr(2, 9);
}
private getCurrentUserId(): string {
// Get from authentication service
return 'user-123';
}
private getExperimentVariant(): string {
// Get from A/B testing service
return Math.random() > 0.5 ? 'variant-a' : 'variant-b';
}
}For NgModule-based applications:
import { NgModule } from '@angular/core';
import { DynamicAttributesModule } from 'ng-dynamic-component';
@NgModule({
imports: [
DynamicAttributesModule
],
// Components can now use dynamic attributes
})
export class MyFeatureModule {}