0
# Dynamic Attributes
1
2
System for dynamically setting and managing HTML attributes on component elements at runtime, supporting both declarative attribute binding and imperative attribute manipulation.
3
4
## Capabilities
5
6
### Dynamic Attributes Directive
7
8
Main directive for dynamically setting HTML attributes on component host elements.
9
10
```typescript { .api }
11
/**
12
* Dynamically sets HTML attributes on component elements
13
* Automatically updates attributes when the attribute map changes
14
*/
15
@Directive({
16
selector: '[ndcDynamicAttributes],[ngComponentOutletNdcDynamicAttributes]',
17
standalone: true,
18
exportAs: 'ndcDynamicAttributes'
19
})
20
export class DynamicAttributesDirective implements DoCheck {
21
/** Attributes for ndc-dynamic components */
22
@Input() ndcDynamicAttributes?: AttributesMap | null;
23
24
/** Attributes for NgComponentOutlet components */
25
@Input() ngComponentOutletNdcDynamicAttributes?: AttributesMap | null;
26
27
/**
28
* Manually set an attribute on the host element
29
* @param name - Attribute name
30
* @param value - Attribute value
31
* @param namespace - Optional namespace for the attribute
32
*/
33
setAttribute(name: string, value: string, namespace?: string): void;
34
35
/**
36
* Manually remove an attribute from the host element
37
* @param name - Attribute name to remove
38
* @param namespace - Optional namespace for the attribute
39
*/
40
removeAttribute(name: string, namespace?: string): void;
41
}
42
```
43
44
### Dynamic Attributes Module
45
46
NgModule that provides dynamic attributes functionality for traditional module-based applications.
47
48
```typescript { .api }
49
/**
50
* Module for dynamic attributes functionality
51
* Includes component outlet integration
52
*/
53
@NgModule({
54
imports: [DynamicAttributesDirective, ComponentOutletInjectorModule],
55
exports: [DynamicAttributesDirective, ComponentOutletInjectorModule]
56
})
57
export class DynamicAttributesModule {}
58
```
59
60
## Types
61
62
```typescript { .api }
63
/**
64
* Map of attribute names to their string values
65
* Used for declarative attribute binding
66
*/
67
interface AttributesMap {
68
[key: string]: string;
69
}
70
```
71
72
**Usage Examples:**
73
74
```typescript
75
import { Component } from '@angular/core';
76
import { DynamicComponent, DynamicAttributesDirective, AttributesMap } from 'ng-dynamic-component';
77
78
// Basic dynamic attributes with ndc-dynamic
79
@Component({
80
standalone: true,
81
imports: [DynamicComponent, DynamicAttributesDirective],
82
template: `
83
<ndc-dynamic
84
[ndcDynamicComponent]="component"
85
[ndcDynamicAttributes]="attributes">
86
</ndc-dynamic>
87
`
88
})
89
export class BasicAttributesExampleComponent {
90
component = MyComponent;
91
92
attributes: AttributesMap = {
93
'data-testid': 'dynamic-component',
94
'aria-label': 'Dynamic content area',
95
'role': 'region',
96
'class': 'theme-dark border-rounded',
97
'style': 'min-height: 200px; background: #f5f5f5;'
98
};
99
}
100
101
// Dynamic attribute changes
102
@Component({
103
template: `
104
<div class="controls">
105
<button (click)="toggleTheme()">Toggle Theme</button>
106
<button (click)="updateAccessibility()">Update Accessibility</button>
107
<button (click)="addCustomAttributes()">Add Custom Attrs</button>
108
</div>
109
110
<ndc-dynamic
111
[ndcDynamicComponent]="component"
112
[ndcDynamicAttributes]="currentAttributes"
113
#dynamicAttrs="ndcDynamicAttributes">
114
</ndc-dynamic>
115
116
<button (click)="imperativeUpdate(dynamicAttrs)">Imperative Update</button>
117
`
118
})
119
export class DynamicAttributesExampleComponent {
120
component = ContentComponent;
121
122
private theme: 'light' | 'dark' = 'light';
123
private accessibilityLevel: 'basic' | 'enhanced' = 'basic';
124
private customAttrsEnabled = false;
125
126
get currentAttributes(): AttributesMap {
127
const attrs: AttributesMap = {
128
'data-theme': this.theme,
129
'class': `theme-${this.theme} component-wrapper`,
130
'role': 'main'
131
};
132
133
// Add accessibility attributes
134
if (this.accessibilityLevel === 'enhanced') {
135
attrs['aria-live'] = 'polite';
136
attrs['aria-describedby'] = 'component-description';
137
attrs['tabindex'] = '0';
138
}
139
140
// Add custom attributes
141
if (this.customAttrsEnabled) {
142
attrs['data-custom'] = 'enabled';
143
attrs['data-timestamp'] = Date.now().toString();
144
attrs['data-version'] = '1.0.0';
145
}
146
147
return attrs;
148
}
149
150
toggleTheme() {
151
this.theme = this.theme === 'light' ? 'dark' : 'light';
152
}
153
154
updateAccessibility() {
155
this.accessibilityLevel = this.accessibilityLevel === 'basic' ? 'enhanced' : 'basic';
156
}
157
158
addCustomAttributes() {
159
this.customAttrsEnabled = !this.customAttrsEnabled;
160
}
161
162
imperativeUpdate(directiveRef: DynamicAttributesDirective) {
163
// Manually set attributes using directive methods
164
directiveRef.setAttribute('data-manual', 'true');
165
directiveRef.setAttribute('data-updated', new Date().toISOString());
166
167
// Set namespaced attribute
168
directiveRef.setAttribute('custom-attr', 'value', 'custom-namespace');
169
170
// Remove an attribute
171
setTimeout(() => {
172
directiveRef.removeAttribute('data-manual');
173
}, 3000);
174
}
175
}
176
```
177
178
### Working with NgComponentOutlet
179
180
Use dynamic attributes with Angular's built-in component outlet:
181
182
```typescript
183
import { ComponentOutletIoDirective, DynamicAttributesDirective } from 'ng-dynamic-component';
184
185
@Component({
186
standalone: true,
187
imports: [ComponentOutletIoDirective, DynamicAttributesDirective],
188
template: `
189
<ng-container
190
*ngComponentOutlet="selectedComponent"
191
[ngComponentOutletNdcDynamicAttributes]="outletAttributes"
192
[ngComponentOutletNdcDynamicInputs]="inputs">
193
</ng-container>
194
`
195
})
196
export class OutletAttributesExampleComponent {
197
selectedComponent = DashboardComponent;
198
199
inputs = {
200
title: 'Dashboard',
201
refreshRate: 5000
202
};
203
204
outletAttributes: AttributesMap = {
205
'data-component': 'dashboard',
206
'aria-label': 'Dashboard content',
207
'class': 'outlet-component dashboard-theme',
208
'data-refresh-rate': '5000'
209
};
210
}
211
```
212
213
### Conditional and Computed Attributes
214
215
Create dynamic attributes based on component state and conditions:
216
217
```typescript
218
@Component({
219
template: `
220
<div class="status-indicators">
221
<span>Loading: {{ isLoading }}</span>
222
<span>Error: {{ hasError }}</span>
223
<span>Theme: {{ currentTheme }}</span>
224
</div>
225
226
<ndc-dynamic
227
[ndcDynamicComponent]="component"
228
[ndcDynamicAttributes]="computedAttributes">
229
</ndc-dynamic>
230
`
231
})
232
export class ConditionalAttributesComponent {
233
component = DataComponent;
234
235
isLoading = false;
236
hasError = false;
237
currentTheme = 'light';
238
userRole = 'user';
239
featureFlags = ['feature-a', 'feature-b'];
240
241
get computedAttributes(): AttributesMap {
242
const attrs: AttributesMap = {};
243
244
// Base attributes
245
attrs['data-component'] = 'data-display';
246
attrs['data-theme'] = this.currentTheme;
247
248
// Conditional attributes based on state
249
if (this.isLoading) {
250
attrs['aria-busy'] = 'true';
251
attrs['data-loading'] = 'true';
252
attrs['class'] = 'component-loading';
253
}
254
255
if (this.hasError) {
256
attrs['aria-invalid'] = 'true';
257
attrs['data-error'] = 'true';
258
attrs['class'] = (attrs['class'] || '') + ' component-error';
259
}
260
261
// Role-based attributes
262
if (this.userRole === 'admin') {
263
attrs['data-admin'] = 'true';
264
attrs['data-permissions'] = 'all';
265
}
266
267
// Feature flag attributes
268
if (this.featureFlags.includes('feature-a')) {
269
attrs['data-feature-a'] = 'enabled';
270
}
271
272
// Computed styles
273
attrs['style'] = this.getComputedStyles();
274
275
return attrs;
276
}
277
278
private getComputedStyles(): string {
279
const styles: string[] = [];
280
281
if (this.isLoading) {
282
styles.push('opacity: 0.6');
283
styles.push('pointer-events: none');
284
}
285
286
if (this.hasError) {
287
styles.push('border: 2px solid red');
288
}
289
290
if (this.currentTheme === 'dark') {
291
styles.push('background: #2d2d2d');
292
styles.push('color: #ffffff');
293
}
294
295
return styles.join('; ');
296
}
297
298
simulateLoading() {
299
this.isLoading = true;
300
setTimeout(() => {
301
this.isLoading = false;
302
this.hasError = Math.random() > 0.7; // 30% chance of error
303
}, 2000);
304
}
305
}
306
```
307
308
### Accessibility and ARIA Attributes
309
310
Dynamically manage accessibility attributes for better user experience:
311
312
```typescript
313
@Component({
314
template: `
315
<div class="accessibility-controls">
316
<label>
317
<input type="checkbox" [(ngModel)]="screenReaderOptimized" />
318
Screen Reader Optimized
319
</label>
320
<label>
321
<input type="checkbox" [(ngModel)]="highContrast" />
322
High Contrast
323
</label>
324
<label>
325
<input type="range" min="1" max="5" [(ngModel)]="complexityLevel" />
326
Complexity Level: {{ complexityLevel }}
327
</label>
328
</div>
329
330
<ndc-dynamic
331
[ndcDynamicComponent]="component"
332
[ndcDynamicAttributes]="accessibilityAttributes">
333
</ndc-dynamic>
334
`
335
})
336
export class AccessibilityAttributesComponent {
337
component = InteractiveComponent;
338
339
screenReaderOptimized = false;
340
highContrast = false;
341
complexityLevel = 3;
342
343
get accessibilityAttributes(): AttributesMap {
344
const attrs: AttributesMap = {
345
'role': 'application',
346
'aria-label': 'Interactive content area'
347
};
348
349
if (this.screenReaderOptimized) {
350
attrs['aria-live'] = 'polite';
351
attrs['aria-atomic'] = 'true';
352
attrs['aria-relevant'] = 'additions text';
353
attrs['aria-describedby'] = 'sr-instructions';
354
}
355
356
if (this.highContrast) {
357
attrs['data-high-contrast'] = 'true';
358
attrs['class'] = 'high-contrast-mode';
359
}
360
361
// Complexity-based attributes
362
attrs['data-complexity'] = this.complexityLevel.toString();
363
364
if (this.complexityLevel <= 2) {
365
attrs['aria-label'] = 'Simple interactive content';
366
attrs['data-ui-mode'] = 'simplified';
367
} else if (this.complexityLevel >= 4) {
368
attrs['aria-label'] = 'Advanced interactive content with multiple features';
369
attrs['data-ui-mode'] = 'advanced';
370
attrs['aria-expanded'] = 'false';
371
}
372
373
return attrs;
374
}
375
}
376
```
377
378
### Data Attributes for Testing and Analytics
379
380
Use dynamic attributes for testing identifiers and analytics tracking:
381
382
```typescript
383
@Component({
384
template: `
385
<ndc-dynamic
386
[ndcDynamicComponent]="component"
387
[ndcDynamicAttributes]="testingAndAnalyticsAttributes">
388
</ndc-dynamic>
389
`
390
})
391
export class TestingAttributesComponent implements OnInit {
392
component = TestableComponent;
393
394
private sessionId = '';
395
private userId = '';
396
private experimentVariant = '';
397
398
ngOnInit() {
399
this.sessionId = this.generateSessionId();
400
this.userId = this.getCurrentUserId();
401
this.experimentVariant = this.getExperimentVariant();
402
}
403
404
get testingAndAnalyticsAttributes(): AttributesMap {
405
const attrs: AttributesMap = {
406
// Testing attributes
407
'data-testid': 'dynamic-component',
408
'data-qa': 'main-content',
409
'data-component-type': this.component.name,
410
411
// Analytics attributes
412
'data-analytics-id': 'dynamic-content',
413
'data-session-id': this.sessionId,
414
'data-user-id': this.userId,
415
'data-page-section': 'main',
416
417
// A/B testing attributes
418
'data-experiment': 'layout-test',
419
'data-variant': this.experimentVariant,
420
421
// Performance tracking
422
'data-track-performance': 'true',
423
'data-component-load-time': Date.now().toString()
424
};
425
426
return attrs;
427
}
428
429
private generateSessionId(): string {
430
return 'session-' + Math.random().toString(36).substr(2, 9);
431
}
432
433
private getCurrentUserId(): string {
434
// Get from authentication service
435
return 'user-123';
436
}
437
438
private getExperimentVariant(): string {
439
// Get from A/B testing service
440
return Math.random() > 0.5 ? 'variant-a' : 'variant-b';
441
}
442
}
443
```
444
445
## Module Usage
446
447
For NgModule-based applications:
448
449
```typescript
450
import { NgModule } from '@angular/core';
451
import { DynamicAttributesModule } from 'ng-dynamic-component';
452
453
@NgModule({
454
imports: [
455
DynamicAttributesModule
456
],
457
// Components can now use dynamic attributes
458
})
459
export class MyFeatureModule {}
460
```