# Template Helpers
System helperów do parsowania i przetwarzania atrybutów Angular w szablonach HTML.
## Architektura
```
helpers/
├── TemplateParser - Parser HTML do drzewa elementów
├── ControlFlowTransformer - Transformacja @if/@else do *ngIf
├── ContentInterpolation - Transformacja {{ }} do [innerText] (w TemplateTransformer)
├── BaseAttributeHelper - Klasa bazowa dla helperów atrybutów
├── StructuralDirectiveHelper - Obsługa dyrektyw strukturalnych (*ngIf, *ngFor)
├── InputBindingHelper - Obsługa bindowania wejść ([property])
├── OutputBindingHelper - Obsługa bindowania wyjść ((event))
├── TwoWayBindingHelper - Obsługa bindowania dwukierunkowego ([(model)])
└── TemplateReferenceHelper - Obsługa referencji do elementów (#ref)
```
## TemplateParser
Parser HTML, który konwertuje szablon na drzewo elementów z wyodrębnionymi atrybutami.
### Interfejsy
```typescript
interface ParsedElement {
tagName: string;
attributes: ParsedAttribute[];
children: (ParsedElement | ParsedTextNode)[];
textContent?: string;
}
interface ParsedTextNode {
type: 'text';
content: string;
}
interface ParsedAttribute {
name: string;
value: string;
type: AttributeType;
}
enum AttributeType {
STRUCTURAL_DIRECTIVE = 'structural',
INPUT_BINDING = 'input',
OUTPUT_BINDING = 'output',
TWO_WAY_BINDING = 'two-way',
TEMPLATE_REFERENCE = 'reference',
REGULAR = 'regular',
}
```
### Użycie
```typescript
const parser = new TemplateParser();
const elements = parser.parse('
Content
');
// Iteracja po wszystkich elementach (pomija text nodes)
parser.traverseElements(elements, (element) => {
console.log(element.tagName, element.attributes);
});
// Przetwarzanie wszystkich węzłów włącznie z text nodes
for (const node of elements) {
if ('type' in node && node.type === 'text') {
console.log('Text:', node.content);
} else {
console.log('Element:', node.tagName);
}
}
```
### Obsługa Text Nodes
Parser automatycznie wykrywa i zachowuje text nodes jako osobne węzły w drzewie:
```typescript
const template = `
Header text
Paragraph
Footer text
`;
const elements = parser.parse(template);
// Zwraca drzewo z text nodes jako osobnymi węzłami typu ParsedTextNode
```
**Cechy text nodes:**
- Zachowują białe znaki (spacje, nowe linie, tabulatory)
- Są pomijane przez `traverseElements()` (tylko elementy HTML)
- Mogą występować na poziomie root lub jako dzieci elementów
- Są prawidłowo rekonstruowane podczas budowania HTML
## ControlFlowTransformer
Transformer konwertujący nową składnię Angular control flow (`@if`, `@else if`, `@else`) na starą składnię z dyrektywami `*ngIf`.
### Transformacje
**Prosty @if:**
```typescript
// Input
@if (isVisible) {
Content
}
// Output
Content
```
**@if z @else:**
```typescript
// Input
@if (isAdmin) {
Admin
} @else {
User
}
// Output
AdminUser
```
**@if z @else if:**
```typescript
// Input
@if (role === 'admin') {
Admin
} @else if (role === 'user') {
User
} @else {
Guest
}
// Output
AdminUserGuest
```
### Użycie
```typescript
import { ControlFlowTransformer } from './control-flow-transformer';
const transformer = new ControlFlowTransformer();
const template = '@if (show) {
Content
}';
const result = transformer.transform(template);
// '
Content
'
```
### Optymalizacja
`ControlFlowTransformer` jest współdzielony między `TemplateProcessor` a innymi komponentami, co zmniejsza rozmiar końcowej aplikacji poprzez uniknięcie duplikacji kodu.
## Content Interpolation
Transformacja interpolacji Angular (`{{ expression }}`) na property binding z `innerText` (bezpieczniejsze niż innerHTML - zapobiega XSS).
### Transformacja
```typescript
// Input
{{ userName }}
Value: {{ data }}
// Output
Value:
```
### Użycie
Transformacja jest częścią `TemplateTransformer`:
```typescript
import { TemplateTransformer } from './processors/template/template-transformer';
const transformer = new TemplateTransformer();
const template = '
{{ message }}
';
const result = transformer.transformInterpolation(template);
// '
'
```
### Obsługa w Runtime
Property bindings `[innerText]` są przetwarzane w runtime przez `TemplateFragment.processPropertyBindings()`:
1. Znajduje wszystkie atrybuty w formacie `[propertyName]`
2. Tworzy `effect` który reaguje na zmiany sygnałów
3. Ewaluuje wyrażenie w kontekście komponentu
4. Przypisuje wartość do właściwości DOM elementu: `element[propertyName] = value`
5. Usuwa atrybut z elementu
Dzięki temu `[innerText]`, `[innerHTML]`, `[textContent]`, `[value]` i inne property bindings działają automatycznie z granularną reaktywnością.
## Fragment Re-rendering
Każdy `ng-container` jest zastępowany parą komentarzy-markerów w DOM:
```html
```
### Zalety
1. **Widoczność w DOM** - można zobaczyć gdzie był `ng-container`
2. **Selektywne re-renderowanie** - można przeładować tylko fragment zamiast całego komponentu
3. **Zachowanie struktury** - markery pokazują granice fragmentu
### API
```typescript
// Pobranie TemplateFragment z elementu
const appRoot = document.querySelector('app-root') as any;
const templateFragment = appRoot.templateFragment;
// Pobranie informacji o markerach
const markers = templateFragment.getFragmentMarkers();
// Returns: Array<{ startMarker, endMarker, condition?, originalTemplate }>
// Re-renderowanie konkretnego fragmentu (po indeksie)
templateFragment.rerenderFragment(0);
// Re-renderowanie wszystkich fragmentów
templateFragment.rerenderAllFragments();
```
### Przykład użycia
```typescript
// Zmiana właściwości komponentu
component.showDetails = true;
// Re-renderowanie fragmentu który zależy od tej właściwości
const markers = templateFragment.getFragmentMarkers();
const detailsFragmentIndex = markers.findIndex(m =>
m.condition?.includes('showDetails')
);
if (detailsFragmentIndex >= 0) {
templateFragment.rerenderFragment(detailsFragmentIndex);
}
```
## BaseAttributeHelper
Abstrakcyjna klasa bazowa dla wszystkich helperów obsługujących atrybuty.
### Interfejs
```typescript
abstract class BaseAttributeHelper {
abstract get supportedType(): string;
abstract canHandle(attribute: ParsedAttribute): boolean;
abstract process(context: AttributeProcessingContext): AttributeProcessingResult;
}
interface AttributeProcessingContext {
element: ParsedElement;
attribute: ParsedAttribute;
filePath: string;
}
interface AttributeProcessingResult {
transformed: boolean;
newAttribute?: ParsedAttribute;
additionalAttributes?: ParsedAttribute[];
removeOriginal?: boolean;
}
```
## Helpery Atrybutów
### StructuralDirectiveHelper
Obsługuje dyrektywy strukturalne Angular.
**Rozpoznawane atrybuty:**
- `*ngIf` - warunkowe renderowanie
- `*ngFor` - iteracja po kolekcji
- `*ngSwitch` - przełączanie widoków
**Przykład:**
```html
Content
{{ item }}
```
### InputBindingHelper
Obsługuje bindowanie właściwości wejściowych.
**Format:** `[propertyName]="expression"`
**Przykład:**
```html
```
### OutputBindingHelper
Obsługuje bindowanie zdarzeń wyjściowych.
**Format:** `(eventName)="handler()"`
**Przykład:**
```html
```
### TwoWayBindingHelper
Obsługuje bindowanie dwukierunkowe (banana-in-a-box).
**Format:** `[(propertyName)]="variable"`
**Przykład:**
```html
```
### TemplateReferenceHelper
Obsługuje referencje do elementów w szablonie.
**Format:** `#referenceName`
**Przykład:**
```html
```
## Tworzenie Własnego Helpera
```typescript
import { BaseAttributeHelper, AttributeProcessingContext, AttributeProcessingResult } from './base-attribute-helper';
import { AttributeType, ParsedAttribute } from './template-parser';
export class CustomAttributeHelper extends BaseAttributeHelper {
get supportedType(): string {
return 'custom-attribute';
}
canHandle(attribute: ParsedAttribute): boolean {
// Logika określająca czy helper obsługuje dany atrybut
return attribute.name.startsWith('custom-');
}
process(context: AttributeProcessingContext): AttributeProcessingResult {
// Logika przetwarzania atrybutu
return {
transformed: true,
newAttribute: {
name: 'data-custom',
value: context.attribute.value,
type: AttributeType.REGULAR,
},
};
}
}
```
## Integracja z TemplateProcessor
Helpery są automatycznie wykorzystywane przez `TemplateProcessor` podczas przetwarzania szablonów:
```typescript
export class TemplateProcessor extends BaseProcessor {
private parser: TemplateParser;
private attributeHelpers: BaseAttributeHelper[];
constructor() {
super();
this.parser = new TemplateParser();
this.attributeHelpers = [
new StructuralDirectiveHelper(),
new InputBindingHelper(),
new OutputBindingHelper(),
new TwoWayBindingHelper(),
new TemplateReferenceHelper(),
// Dodaj własne helpery tutaj
];
}
}
```
## Kolejność Przetwarzania
1. **Parsowanie** - `TemplateParser` konwertuje HTML na drzewo elementów
2. **Transformacja control flow** - `@if/@else` → `*ngIf`
3. **Przetwarzanie atrybutów** - Helpery przetwarzają atrybuty każdego elementu
4. **Rekonstrukcja** - Drzewo jest konwertowane z powrotem na HTML
5. **Escapowanie** - Znaki specjalne są escapowane dla template string
## Wykrywanie Typów Atrybutów
Parser automatycznie wykrywa typ atrybutu na podstawie składni:
| Składnia | Typ | Helper |
|----------|-----|--------|
| `*ngIf` | STRUCTURAL_DIRECTIVE | StructuralDirectiveHelper |
| `[property]` | INPUT_BINDING | InputBindingHelper |
| `(event)` | OUTPUT_BINDING | OutputBindingHelper |
| `[(model)]` | TWO_WAY_BINDING | TwoWayBindingHelper |
| `#ref` | TEMPLATE_REFERENCE | TemplateReferenceHelper |
| `class` | REGULAR | - |
## Runtime Bindings (TemplateFragment)
Podczas renderowania szablonu w runtime, `TemplateFragment` przetwarza bindingi:
### Input Bindings dla Custom Elements
Dla elementów z `-` w nazwie (custom elements), input bindings tworzą signale w `__inputs`:
```html
```
```typescript
// W runtime element będzie miał:
element.__inputs = {
mode: WritableSignal, // signal z wartością currentMode
title: WritableSignal, // signal z wartością pageTitle
};
// W komponencie można odczytać:
const mode = this._nativeElement.__inputs?.['mode']?.();
```
### Output Bindings
Output bindings są obsługiwane przez `addEventListener`:
```html
```
```typescript
// W runtime dodawane są event listenery:
element.addEventListener('menuClick', (event) => {
component.toggleMenu();
});
element.addEventListener('search', (event) => {
component.onSearch(event);
});
```
### Specjalne Bindingi
#### `[attr.X]` - Attribute Binding
```html
```
- `true` → ustawia pusty atrybut
- `false/null/undefined` → usuwa atrybut
- inne wartości → ustawia jako string
#### `[style.X]` - Style Binding
```html
```
- Obsługuje camelCase (`fontSize`) i kebab-case (`font-size`)
- `false/null/undefined` → usuwa właściwość stylu
#### `[class.X]` - Class Binding
```html
```
- `true` → dodaje klasę
- `false` → usuwa klasę
### DOM Property Bindings
Dla zwykłych elementów HTML, bindingi ustawiają właściwości DOM:
```html
```