Ir al contenido

NgxControlValueAccessor

Created by Robby Rabbitman

NgxControlValueAccessor es una directiva para reducir la redundancia al construir componentes que implementan la interfaz ControlValueAccessor.

import { NgxControlValueAccessor } from 'ngxtension/control-value-accessor';

NgxControlValueAccessor implementa la interfaz ControlValueAccessor y expone una api más simple. Declara NgxControlValueAccessor en la sección hostDirectives de tu componente e inyecta la instancia para conectar tu plantilla:

  • NgxControlValueAccessor.value para sincronizar el valor.
  • NgxControlValueAccessor.disabled para sincronizar el estado deshabilitado.
  • NgxControlValueAccessor.markAsTouched para marcar la vista como touched.

El valor y el estado deshabilitado también están disponibles como señales:

  • NgxControlValueAccessor.value$
  • NgxControlValueAccessor.disabled$

En este ejemplo, NgxControlValueAccessor se utiliza para crear un componente CustomInput.

@Component({
selector: 'custom-input',
hostDirectives: [NgxControlValueAccessor],
template: `
<label>
<b>Etiqueta personalizada</b>
<input
type="text"
(input)="cva.value = $event.target.value"
[value]="cva.value$()"
[disabled]="cva.disabled$()"
(blur)="cva.markAsTouched()"
/>
</label>
`,
standalone: true,
})
export class CustomInput {
protected cva = inject<NgxControlValueAccessor<string>>(
NgxControlValueAccessor,
);
}

Con el uso:

<custom-input [formControl]="control" />
<custom-input [(ngModel)]="value" />

Cuando tu modelo es un tipo de dato no primitivo, debes proporcionar un comparador. Es una función pura que le dice a NgxControlValueAccessor si dos valores son semánticamente iguales:

(a, b) => boolean;

En este ejemplo, NgxControlValueAccessor se utiliza para crear un selector de Usuario. Un Usuario se identifica por su id.

interface Usuario {
id: string;
nombre: string;
}
const comparadorUsuario: NgxControlValueAccessorCompareTo<Usuario> = (a, b) =>
a?.id === b?.id;
provideCvaCompareTo(comparadorUsuario, true);
// o
provideCvaCompareToByProp<Usuario>('id');

Ejemplo completo:

@Component({
selector: 'selector-usuario',
standalone: true,
hostDirectives: [NgxControlValueAccessor],
providers: [provideCvaCompareToByProp<Usuario>('id')],
template: `
<label>
<b>Selecciona un usuario:</b>
<select
[disabled]="cva.disabled$()"
(blur)="cva.markAsTouched()"
(change)="onChange($event)"
>
<option [selected]="cva.value === null">
-- ningún usuario seleccionado --
</option>
@for (usuario of usuarios; track usuario.id) {
<option
[value]="usuario.id"
[selected]="usuario.id === cva.value?.id"
>
{{ usuario.nombre }}
</option>
}
</select>
</label>
`,
})
export class SelectorUsuario {
protected cva = inject<NgxControlValueAccessor<Usuario | null>>(
NgxControlValueAccessor,
);
protected onChange = (event: Event) =>
(this.cva.value =
this.usuarios.find(({ id }) => event.target.value === id) ?? null);
@Input()
usuarios: Usuario[] = [];
}

Con uso:

<selector-usuario [formControl]="controlUsuario" [usuarios]="usuarios" />
<selector-usuario [(ngModel)]="usuario" [usuarios]="usuarios" />

Opcionalmente, puedes exponer inputs y outputs en la declaración de hostDirectives y usarlo sin una directiva NgControl.

hostDirectives: [
{
directiva: NgxControlValueAccessor,
inputs: ['value'],
outputs: ['valueChange'],
},
];
<custom-input [(value)]="valor" />