refactor: migra los campos de formulario y sliders a Signal Forms
This commit is contained in:
@@ -16,19 +16,83 @@ Construir: `nx build ui` · Lint: `nx lint ui`
|
||||
|
||||
## Qué se hizo
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Prefijo | `Dl`→`Pi`, `dl-`→`pi-` (selectores, clases CSS, nombres de fichero), `@deale-workspace/…`→rutas relativas |
|
||||
| NgModules | 41 → 4 (sólo quedan los que aportan providers: `icon-registry`, `loaders`, `router-store`, `scroll-to`) |
|
||||
| Componentes | 70, **todos** con `ChangeDetectionStrategy.OnPush` |
|
||||
| Decoradores | 0 restantes: 460 `input()`, 6 `model()`, 20 `output()`, 8 `viewChild()`, 2 `viewChildren()`, 17 `contentChild()`, 6 `contentChildren()` |
|
||||
| Control flow | `*ngIf`/`*ngFor`/`*ngSwitch` → `@if`/`@for`/`@switch` (110 `@if`, 26 `@for`, 3 `@switch`). Ya no se importa ninguna directiva de `CommonModule` |
|
||||
| | |
|
||||
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Prefijo | `Dl`→`Pi`, `dl-`→`pi-` (selectores, clases CSS, nombres de fichero), `@deale-workspace/…`→rutas relativas |
|
||||
| NgModules | 41 → 4 (sólo quedan los que aportan providers: `icon-registry`, `loaders`, `router-store`, `scroll-to`) |
|
||||
| Componentes | 70, **todos** con `ChangeDetectionStrategy.OnPush` |
|
||||
| Decoradores | 0 restantes: 460 `input()`, 6 `model()`, 20 `output()`, 8 `viewChild()`, 2 `viewChildren()`, 17 `contentChild()`, 6 `contentChildren()` |
|
||||
| Control flow | `*ngIf`/`*ngFor`/`*ngSwitch` → `@if`/`@for`/`@switch` (110 `@if`, 26 `@for`, 3 `@switch`). Ya no se importa ninguna directiva de `CommonModule` |
|
||||
| Formularios | Los 13 campos de `ui/form-field` y los 2 de `ui/slider` pasan de `ControlValueAccessor` + `formControlName` a **Signal Forms** (`FormValueControl` / `FormCheckboxControl`) |
|
||||
|
||||
Se usaron las migraciones oficiales de Angular (`@angular/core:standalone`,
|
||||
`signal-input-migration`, `output-migration`, `signal-queries-migration`,
|
||||
`control-flow-migration`, `cleanup-unused-imports`) y se resolvió a mano lo que
|
||||
no cubren.
|
||||
|
||||
## Formularios: Signal Forms
|
||||
|
||||
Los campos ya no son `ControlValueAccessor` ni reciben `formControlName`.
|
||||
Implementan los contratos de Signal Forms, así que basta con enlazar `[field]`:
|
||||
|
||||
```ts
|
||||
import { form, required, min, FormField } from '@angular/forms/signals';
|
||||
|
||||
@Component({
|
||||
imports: [FormField, PiTextFieldComponent, PiNumberFieldComponent, PiSlideToggleComponent],
|
||||
template: `
|
||||
<pi-text-field [field]="f.name" placeholder="Nombre" />
|
||||
<pi-number-field [field]="f.age" />
|
||||
<pi-slide-toggle [field]="f.active" />
|
||||
`,
|
||||
})
|
||||
export class Ejemplo {
|
||||
private readonly model = signal({ name: '', age: null, active: false });
|
||||
|
||||
protected readonly f = form(this.model, (m) => {
|
||||
required(m.name);
|
||||
min(m.age, 18);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
La directiva `FormField` sincroniza sola `value`, `disabled`, `errors`,
|
||||
`touched`, `required`, `min`/`max`, `maxLength`… con el campo enlazado.
|
||||
|
||||
| Componente | Contrato | Tipo del valor |
|
||||
| ---------------------------------------------------- | --------------------- | ------------------------------------- |
|
||||
| text, email, password, phone, icon, search, textarea | `FormValueControl` | `string` |
|
||||
| number, currency, incremental-number | `FormValueControl` | `number \| null` |
|
||||
| slide-toggle | `FormCheckboxControl` | `boolean` (usa `checked`, no `value`) |
|
||||
| incremental-range, range-slider | `FormValueControl` | `PiRange` (`{from, to}`) |
|
||||
| slider | `FormValueControl` | `number` |
|
||||
|
||||
Detalles a tener en cuenta:
|
||||
|
||||
- **`formControlName` y `formGroupName` desaparecen.** `incremental-range-field`
|
||||
y `range-slider` ya no leen un `FormGroup` anidado: reciben el rango completo
|
||||
por `value` (tipo `PiRange`, exportado por la librería).
|
||||
- **`isDisabled` → `disabled`**, que es el nombre del contrato y ahora es una
|
||||
señal (`disabled()`).
|
||||
- **Los mensajes de error** venían de `control.errors['message']` (un array de
|
||||
claves i18n). Ahora se leen del input `errors`, usando `error.message` y
|
||||
cayendo a `error.kind` si no lo hay. La clase base expone `errorMessages()`.
|
||||
- **Los campos numéricos usan `transformedValue`**, el mecanismo de Signal Forms
|
||||
para parsear texto→número reportando errores de parseo al campo.
|
||||
- **`ngx-currency` se ha eliminado de `currency-field`.** Su directiva
|
||||
`currencyMask` es un ControlValueAccessor y no funciona sin reactive forms.
|
||||
El formateo (decimal `,`, miles `.`, 2 decimales) se replica con
|
||||
`transformedValue` + `toLocaleString('es-ES')`. **Se pierde el enmascarado en
|
||||
vivo mientras se escribe**: ahora se formatea al salir del campo.
|
||||
- **`slide-toggle` ya no extiende `PiBaseFormFieldComponent`**: el contrato de
|
||||
casilla exige `checked` y prohíbe declarar `value`. También desaparece el
|
||||
campo `isChecked` que se sincronizaba por suscripción.
|
||||
- **`touch`**: los controles emiten esta salida en el `blur` para que el campo
|
||||
se marque como tocado.
|
||||
|
||||
Los selects (`ui/select`) siguen con `ControlValueAccessor`; quedaron fuera del
|
||||
alcance de esta migración.
|
||||
|
||||
## Decisiones que cambian comportamiento
|
||||
|
||||
Revisar estos puntos antes de usar la librería en producción:
|
||||
@@ -57,7 +121,7 @@ durante la detección de cambios, no de forma síncrona en la asignación:
|
||||
**3. `PiAccordionComponent.expandedItemIndex` pasa a `model()`.**
|
||||
Tenía un two-way binding hecho a mano (`expandedItemIndex` + `expandedItemIndexChange`).
|
||||
`model()` genera el evento automáticamente. **Ojo:** el código original emitía el
|
||||
valor *anterior* al plegar/desplegar (`emit(expandedItemIndex)` antes de reasignar);
|
||||
valor _anterior_ al plegar/desplegar (`emit(expandedItemIndex)` antes de reasignar);
|
||||
`model()` emite el nuevo. Parece que el original tenía un bug. Su tipo pasa a
|
||||
`number | null` porque `toggleState` asigna `null` al colapsar.
|
||||
|
||||
@@ -97,10 +161,10 @@ corrigió la aridad a `track trackByFn(i)`.
|
||||
El origen tiene símbolos duplicados. En `index.ts` gana el primero y el otro se
|
||||
omite (queda anotado en el fichero). Siguen siendo accesibles por ruta directa:
|
||||
|
||||
| Símbolo | Se exporta desde | Se omite de |
|
||||
|---|---|---|
|
||||
| `PiSlideToggleComponent` | `ui/form-field` | `ui/slide-toggle` |
|
||||
| `PiDisabledDirective` | `util/status` | `ui/text` |
|
||||
| Símbolo | Se exporta desde | Se omite de |
|
||||
| ----------------------------------------------- | --------------------- | ------------------- |
|
||||
| `PiSlideToggleComponent` | `ui/form-field` | `ui/slide-toggle` |
|
||||
| `PiDisabledDirective` | `util/status` | `ui/text` |
|
||||
| `featureKey`, `getFeatureState`, `initialState` | `util/device-configs` | `util/list-configs` |
|
||||
|
||||
## APIs de terceros actualizadas
|
||||
|
||||
Reference in New Issue
Block a user