Posterior a buscar en varios repositorios y páginas webs de internet finalmente hemos dado con la respuesta que te mostraremos a continuación.
Solución:
Me encontré con el mismo problema hace algún tiempo y quiero compartir un ejemplo mínimo que funciona con Angular 2+.
Para las versiones más nuevas de Angular, hay un enfoque simplificado (desplácese hacia abajo).
Angular 2+
Suponga que le gustaría usar el siguiente código en cualquier lugar de su aplicación:
-
Ahora crea un componente llamado
InputSlider
. -
En el
input-slider.component.html
, agregue lo siguiente: -
Ahora tenemos que trabajar un poco en el
input-slider.component.ts
expediente:import Component, forwardRef, OnInit from "@angular/core"; import ControlValueAccessor, NG_VALUE_ACCESSOR from "@angular/forms"; @Component( selector: "app-input-slider", templateUrl: "./input-slider.component.html", styleUrls: ["./input-slider.component.scss"], providers: [ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputSliderComponent), multi: true ] ) export class InputSliderComponent implements ControlValueAccessor /** * Holds the current value of the slider */ value: number = 0; /** * Invoked when the model has been changed */ onChange: (_: any) => void = (_: any) => ; /** * Invoked when the model has been touched */ onTouched: () => void = () => ; constructor() /** * Method that is invoked on an update of a model. */ updateChanges() this.onChange(this.value); /////////////// // OVERRIDES // /////////////// /** * Writes a new item to the element. * @param value the value */ writeValue(value: number): void this.value = value; this.updateChanges(); /** * Registers a callback function that should be called when the control's value changes in the UI. * @param fn */ registerOnChange(fn: any): void this.onChange = fn; /** * Registers a callback function that should be called when the control receives a blur event. * @param fn */ registerOnTouched(fn: any): void this.onTouched = fn;
Por supuesto, podría agregar más funciones y verificaciones de valor usando esta clase, pero espero que le brinde algunas ideas.
Explicación rápida:
El truco consiste en agregar el proveedor NG_VALUE_ACCESSOR
en el decorador de la clase e implementar ControlValueAccessor
.
Entonces necesitamos definir las funciones writeValue
, registerOnChange
y registerOnTouched
. Los dos últimos se solicitan directamente en la creación del componente. Es por eso que necesitamos variables (por ejemplo onChange
y onTouched
– pero puedes nombrarlos como quieras.
Finalmente, necesitamos definir una función que le permita al componente actualizar el ngModel subyacente. Hice eso con la función updateChanges
. Debe invocarse siempre que cambie el valor, ya sea desde fuera (por eso se llama en writeValue
), o desde adentro (por eso se llama desde el html ngModelChange
).
Angular 7+
Si bien el primer enfoque aún funciona para las versiones más nuevas, es posible que prefiera la siguiente versión que necesita menos escritura.
En días anteriores, lograría un enlace bidireccional agregando algo como esto en el componente externo:
Angular implementó azúcar sintáctico para eso, por lo que ahora puede escribir
si sigue los pasos a continuación.
-
Crea un componente llamado
InputSlider
. -
En el
input-slider.component.html
, agregue lo siguiente: -
Ahora tenemos que trabajar un poco en el
input-slider.component.ts
expediente:import Component, forwardRef, OnInit from "@angular/core"; @Component( selector: "app-input-slider", templateUrl: "./input-slider.component.html", styleUrls: ["./input-slider.component.scss"], providers: [] ) export class InputSliderComponent /** * Holds the current value of the slider */ @Input() inputSliderValue: string = ""; /** * Invoked when the model has been changed */ @Output() inputSliderValueChange: EventEmitter
= new EventEmitter ();
Es importante que la propiedad de salida (EventEmitter) tenga el mismo nombre que la propiedad de entrada con el apéndice string Change
.
Si comparamos ambos enfoques, notamos lo siguiente:
- El primer enfoque le permite utilizar
[(ngModel)]="propertyNameOutsideTheComponent"
como si el componente fuera cualquier elemento de formulario. - Solo el primer enfoque le permite usar la validación directamente (para formularios).
- Pero el primer enfoque necesita más codificación dentro de la clase de componente que el segundo enfoque
- El segundo enfoque le permite usar un enlace bidireccional en su propiedad con la sintaxis
[(propertyNameInsideTheComponent)]="propertyNameOutsideTheComponent"
esto también se puede hacer así, cuando crea un enlace bidireccional [()] puede vincularlo a una función usando el mismo nombre + ‘cambiar’ (en nuestro caso inputModel y inputModelChange) de esta manera el ngModel se actualizará cuando active inputModelChange.emit (‘updatedValue’). y solo necesita declararlo una vez dentro de su componente.
app-input.component.ts
import Component, OnInit, Output, Input, EventEmitter from '@angular/core';
@Component(
selector: 'app-input',
template: ` `,
styleUrls: ['./app-input.component.scss']
)
export class AppInputComponent
@Input() inputModel: string;
@Output() inputModelChange = new EventEmitter();
app.component.html
Si no le importa vincular su variable por [ngModel]
en modelo de plantilla o [formControl]
en forma reactiva, puede usar la respuesta omer.
De lo contrario:
-
Agregar
NG_VALUE_ACCESSOR
token de inyección en la definición de su componente:import ControlValueAccessor, NG_VALUE_ACCESSOR from '@angular/forms'; @Component( ..., providers: [ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AppInputComponent), multi: true ] )
-
Implementar
ControlValueAccessor
interfaz:export class AppInputComponent implements ControlValueAccessor writeValue(obj: any): void // Step 3 registerOnChange(fn: any): void this.onChange = fn; registerOnTouched(fn: any): void this.onTouched = fn; setDisabledState?(isDisabled: boolean): void onChange: any = () => ; onTouched: any = () => ;
-
Administrar
value
cuando cambia:private _value; public get value() return this._value; public set value(v) this._value = v; this.onChange(this._value); this.onTouched(); writeValue(obj: any): void this._value = obj; // Optional onSomeEventOccured(newValue) this.value = newValue;
Ahora puedes usar