Saltar al contenido

¿Cómo crear un componente de entrada personalizado con ngModel trabajando en angular 6?

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:


  1. Ahora crea un componente llamado InputSlider.

  2. En el input-slider.component.html, agregue lo siguiente:

    
    
  3. 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.

  1. Crea un componente llamado InputSlider.

  2. En el input-slider.component.html, agregue lo siguiente:

     
    
  3. 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:

  1. 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
         
       ]
    )
    
  2. 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 = () =>  ;
    
    
    
  3. 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

¡Haz clic para puntuar esta entrada!
(Votos: 2 Promedio: 4)


Tags : /

Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *